summaryrefslogtreecommitdiff
path: root/web/login.hoon
diff options
context:
space:
mode:
authorpolwex <polwex@sortug.com>2025-06-22 23:11:11 +0700
committerpolwex <polwex@sortug.com>2025-06-22 23:11:11 +0700
commit6fb80b2d94a5282c8350278e299bfcb2d0b60d40 (patch)
treebe85636f67322948181bf59519dfe998f7d7b6b1 /web/login.hoon
parent4e2a84761b95a29c02c77c575810ab49f2af7335 (diff)
Diffstat (limited to 'web/login.hoon')
-rw-r--r--web/login.hoon311
1 files changed, 18 insertions, 293 deletions
diff --git a/web/login.hoon b/web/login.hoon
index b4b1084..aa1d57d 100644
--- a/web/login.hoon
+++ b/web/login.hoon
@@ -1,291 +1,8 @@
-|_ bowl:gall
-++ auth-styling
- '''
- @import url("https://rsms.me/inter/inter.css");
- @font-face {
- font-family: "Source Code Pro";
- src: url("https://storage.googleapis.com/media.urbit.org/fonts/scp-regular.woff");
- font-weight: 400;
- font-display: swap;
- }
- :root {
- --red-soft: #FFEFEC;
- --red: #FF6240;
- --gray-100: #E5E5E5;
- --gray-400: #999999;
- --gray-800: #333333;
- --white: #FFFFFF;
- }
- html {
- font-family: Inter, sans-serif;
- height: 100%;
- margin: 0;
- width: 100%;
- background: var(--white);
- color: var(--gray-800);
- -webkit-font-smoothing: antialiased;
- line-height: 1.5;
- font-size: 16px;
- font-weight: 600;
- display: flex;
- flex-flow: row nowrap;
- justify-content: center;
- }
- body {
- display: flex;
- flex-flow: column nowrap;
- justify-content: center;
- max-width: 300px;
- padding: 1rem;
- width: 100%;
- }
- body.local #eauth,
- body.eauth #local {
- display: none;
- min-height: 100%;
- }
- #eauth input {
- /*NOTE dumb hack to get approx equal height with #local */
- margin-bottom: 15px;
- }
- body nav {
- background: var(--gray-100);
- border-radius: 2rem;
- display: flex;
- justify-content: space-around;
- overflow: hidden;
- margin-bottom: 1rem;
- }
- body nav div {
- width: 50%;
- padding: 0.5rem 1rem;
- text-align: center;
- cursor: pointer;
- }
- body.local nav div.local,
- body.eauth nav div.eauth {
- background: var(--gray-800);
- color: var(--white);
- cursor: default;
- }
- nav div.local {
- border-right: none;
- border-top-right-radius: 0;
- border-bottom-right-radius: 0;
- }
- nav div.eauth {
- border-left: none;
- border-top-left-radius: 0;
- border-bottom-left-radius: 0;
- }
- body > *,
- form > input {
- width: 100%;
- }
- form {
- display: flex;
- flex-flow: column;
- align-items: flex-start;
- }
- input {
- background: var(--gray-100);
- border: 2px solid transparent;
- padding: 0.5rem;
- border-radius: 0.5rem;
- font-size: inherit;
- color: var(--gray-800);
- box-shadow: none;
- width: 100%;
- }
- input:disabled {
- background: var(--gray-100);
- color: var(--gray-400);
- }
- input:focus {
- outline: none;
- background: var(--white);
- border-color: var(--gray-400);
- }
- input:invalid:not(:focus) {
- background: var(--red-soft);
- border-color: var(--red);
- outline: none;
- color: var(--red);
- }
- button[type=submit] {
- margin-top: 1rem;
- }
- button[type=submit], a.button {
- font-size: 1rem;
- padding: 0.5rem 1rem;
- border-radius: 0.5rem;
- background: var(--gray-800);
- color: var(--white);
- border: none;
- font-weight: 600;
- text-decoration: none;
- }
- input:invalid ~ button[type=submit] {
- border-color: currentColor;
- background: var(--gray-100);
- color: var(--gray-400);
- pointer-events: none;
- }
- span.guest, span.guest a {
- color: var(--gray-400);
- }
- span.failed {
- display: flex;
- flex-flow: row nowrap;
- height: 1rem;
- align-items: center;
- margin-top: 0.875rem;
- color: var(--red);
- }
- span.failed svg {
- height: 1rem;
- margin-right: 0.25rem;
- }
- span.failed path {
- fill: transparent;
- stroke-width: 2px;
- stroke-linecap: round;
- stroke: currentColor;
- }
- .mono {
- font-family: 'Source Code Pro', monospace;
- }
- @media all and (prefers-color-scheme: dark) {
- :root {
- --white: #000000;
- --gray-800: #E5E5E5;
- --gray-400: #808080;
- --gray-100: #333333;
- --red-soft: #7F1D1D;
- }
- }
- @media screen and (min-width: 30em) {
- html {
- font-size: 14px;
- }
- }
- '''
+|_ [bowl:gall desk=tape redirect-str=tape]
++ favicon %+
weld "<svg width='10' height='10' viewBox='0 0 10 10' xmlns='http://www.w3.org/2000/svg'>"
"<circle r='3.09' cx='5' cy='5' /></svg>"
-++ landscape ^- manx
-=/ eauth=(unit ?) [~ %|]
-=/ failed=? .n
-=/ identity=identity:eyre [%ours ~]
-=/ redirect-url=(unit @t) ~
-=/ redirect-str ?~(redirect-url "" (trip u.redirect-url))
- ;html
- ;head
- ;meta(charset "utf-8");
- ;meta(name "viewport", content "width=device-width, initial-scale=1, shrink-to-fit=no");
- ;link(rel "icon", type "image/svg+xml", href (weld "data:image/svg+xml;utf8," favicon));
- ;title:"Urbit"
- ;style:"{(trip auth-styling)}"
- ;style:"{?^(eauth "" "nav \{ display: none; }")}"
- ;script:"our = '{(scow %p our)}';"
- ;script:'''
- let name, pass;
- function setup(isEauth) {
- name = document.getElementById('name');
- pass = document.getElementById('pass');
- if (isEauth) goEauth(); else goLocal();
- }
- function goLocal() {
- document.body.className = 'local';
- pass.focus();
- }
- function goEauth() {
- document.body.className = 'eauth';
- name.focus();
- }
- function doEauth() {
- if (name.value == our) {
- event.preventDefault();
- goLocal();
- }
- }
- '''
- ==
- ;body
- =class "{?:(=(`& eauth) "eauth" "local")}"
- =onload "setup({?:(=(`& eauth) "true" "false")})"
- ;div#local
- ;p:"Urbit ID"
- ;input(value "{(scow %p our)}", disabled "true", class "mono");
- ;+ ?: =(%ours -.identity)
- ;div
- ;p:"Already authenticated"
- ;a.button/"{(trip (fall redirect-url '/'))}":"Continue"
- ==
- ;form(action "/~/login", method "post", enctype "application/x-www-form-urlencoded")
- ;p:"Access Key"
- ;input
- =type "password"
- =name "password"
- =id "pass"
- =placeholder "sampel-ticlyt-migfun-falmel"
- =class "mono"
- =required "true"
- =minlength "27"
- =maxlength "27"
- =pattern "((?:[a-z]\{6}-)\{3}(?:[a-z]\{6}))";
- ;input(type "hidden", name "redirect", value redirect-str);
- ;+ ?. failed ;span;
- ;span.failed
- ;svg(xmlns "http://www.w3.org/2000/svg", viewBox "0 0 16 16")
- ;path(d "m8 8 4-4M8 8 4 4m4 4-4 4m4-4 4 4");
- ==
- Key is incorrect
- ==
- ;button(type "submit"):"Continue"
- ==
- ==
- ;div#eauth
- ;form(action "/~/login", method "post", onsubmit "return doEauth()")
- ;p:"Urbit ID Metamask Login"
- ;input.mono
- =name "name"
- =id "name"
- =placeholder "{(scow %p our)}"
- =required "true"
- =minlength "4"
- =maxlength "57"
- =pattern "~((([a-z]\{6})\{1,2}-\{0,2})+|[a-z]\{3})";
- ;p
- ; You will be redirected to your own web interface to authorize
- ; logging in to
- ;span.mono:"{(scow %p our)}"
- ; .
- ==
- ;input(type "hidden", name "redirect", value redirect-str);
- ;button(name "eauth", type "submit"):"Continue"
- ==
- ==
- ;* ?: ?=(%ours -.identity) ~
- =+ as="proceed as{?:(?=(%fake -.identity) " guest" "")}"
- ;+ ;span.guest.mono
- ; Or try to
- ;a/"{(trip (fall redirect-url '/'))}":"{as}"
- ; .
- ==
- ==
- ;script:'''
- var failSpan = document.querySelector('.failed');
- if (failSpan) {
- document.querySelector("input[type=password]")
- .addEventListener('keyup', function (event) {
- failSpan.style.display = 'none';
- });
- }
- '''
- ==
++ $
-=/ redirect-str "/forum"
;html
;head
;meta(charset "utf-8");
@@ -294,7 +11,7 @@
==
;body
;main#login-page.white
- ;h1.tc:"Zodiac Login"
+ ;h1.tc:"Login"
;button(id "mauth"):"Login via 🦊MetaMask »"
;script(type "module"):"{metamask-script}"
:: ;script(type "importmap"):"{import-script}"
@@ -316,8 +33,17 @@
"ethers": "/node_modules/ethers/"
}}
'''
+++ fetch-urls ^- tape
+"""
+const sigilUrl = "/{desk}/sigil/";
+const secretUrl= "/{desk}/metamask";
+const authUrl= "/{desk}/auth";
+const redirectUrl = "{redirect-str}";
+
+"""
++ metamask-script
^~
+ %+ weld fetch-urls
%- trip
'''
import { ethers } from "https://cdnjs.cloudflare.com/ajax/libs/ethers/6.7.0/ethers.min.js";
@@ -377,7 +103,7 @@
async function fetchSigil(ship) {
try {
- const response = await fetch('/zodiac/sigil/' + ship);
+ const response = await fetch(sigilUrl + ship);
if (response.ok) {
const data = await response.text();
return data;
@@ -390,7 +116,7 @@
}
async function fetchSecret() {
try {
- const response = await fetch('/zodiac/metamask');
+ const response = await fetch(secretUrl);
if (response.ok) {
const data = await response.json();
return data.challenge;
@@ -428,21 +154,20 @@
async function metamaskLogin(account, point){
// Fetch the secret from the server
- const secret = await fetchSecret();
- console.log({secret});
+ const challenge = await fetchSecret();
const signature = await window.ethereum.request({
method: "personal_sign",
- params: [secret, account],
+ params: [challenge, account],
});
- const response = await fetch('/zodiac/auth', {
+ const response = await fetch(authUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
who: Number(point),
- secret: secret,
+ challenge,
address: account,
signature: signature
}),
@@ -450,7 +175,7 @@
if (response.ok) {
// location.reload();
- window.location.replace('/zodiac');
+ window.location.replace(redirectUrl);
} else {
alert("Login failed. Please try again.");
}