@e280/authlocal
Version:
User-sovereign login system for everybody
170 lines (145 loc) • 4.05 kB
text/typescript
import {html, shadowView} from "@benev/slate"
import stylesCss from "./styles.css.js"
import themeCss from "../../../theme.css.js"
import {manager} from "../../../context.js"
import {constants} from "../../../../constants.js"
import {hostcode} from "../../../utils/hostcode.js"
import {Situation} from "../../../logic/situation.js"
import {SeedReveal} from "../../common/seed-reveal/view.js"
import {IdentityDraft} from "../../common/identity-widget/draft.js"
import {IdentityWidget} from "../../common/identity-widget/view.js"
import {crushUsername} from "../../../../common/utils/crush-username.js"
import {generateIdentity, seedPack} from "../../../../trust/exports/authority.js"
export const CreatePage = shadowView(use => (situation: Situation.Create) => {
use.name("create-page")
use.styles([themeCss, stylesCss])
const draft = use.once(() => new IdentityDraft(situation.initialIdentity))
const first = situation.identities.length === 0
const purpose = manager.purpose.value
const wizard = use.signal<"editor" | "seeder">("editor")
const finalized = use.signal({
identity: situation.initialIdentity,
seed: situation.initialIdentitySeed,
})
const editor = use.once(() => {
async function reroll() {
const freshIdentity = await generateIdentity()
draft.identity = freshIdentity
}
async function clickCreate() {
const identity = draft.getValidEditedIdentity()
if (identity) {
await situation.onSave(identity)
const seed = await seedPack(identity)
finalized.value = {identity: identity, seed}
wizard.value = "seeder"
}
}
const render = () => {
return html`
<div theme-group>
<h2>
${purpose.kind === "login"
? html`${hostcode(purpose.appOrigin)} wants your login`
: (first
? html`Create your first identity`
: html`Create a new identity`)}
</h2>
<p>Choose your public username</p>
</div>
${IdentityWidget([draft, {editable: true}])}
<footer theme-buttons>
${situation.onBack ? html`
<button
theme-button=back
theme-hush
@click="${situation.onBack}">
Cancel
</button>
` : null}
<button
theme-button
theme-hush
@click="${situation.onIngress}">
Import
</button>
<button
theme-button
theme-hush
@click="${reroll}">
Randomize
</button>
<button
theme-button=happy
theme-loud
?disabled="${!draft.getValidEditedIdentity()}"
@click="${clickCreate}">
Create
</button>
</footer>
`
}
return {render, draft, reroll}
})
const seeder = use.once(() => {
function login() {
const identity = editor.draft.getValidEditedIdentity()
if (purpose.kind === "login" && identity)
purpose.onIdentity(identity)
}
async function clickDone() {
await editor.reroll()
wizard.value = "editor"
situation.onDone()
}
function render() {
const {identity, seed} = finalized.value
return html`
${purpose.kind === "login"
? html`<h2>
${hostcode(purpose.appOrigin)}
<span>wants your login</span>
</h2>`
: null}
<section theme-group=tight>
<h2>Save your recovery seed</h2>
<p>Keep it safe. Never share it.</p>
</section>
<section theme-group>
${SeedReveal([seed, crushUsername(identity.label) + constants.seedExtension])}
</section>
<footer theme-buttons>
${purpose.kind === "login" ? html`
<button
theme-button=back
theme-hush
@click="${clickDone}">
Done
</button>
<button
theme-button=login
theme-loud
@click="${login}">
Login
</button>
` : html`
<button
theme-button=happy
theme-loud
@click="${clickDone}">
Continue
</button>
`}
</footer>
`
}
return {render}
})
return html`
<section theme-plate x-wizard="${wizard.value}">
${wizard.value === "editor"
? editor.render()
: seeder.render()}
</section>
`
})