import { h, Component, render } from "preact" import "inputs" import "./u2f-api-polyfill" import sha from "sha512"; import { setCookie, getCookie } from "cookie" let appname = "test"; function Loader() { return
} class Username extends Component<{ username: string, onNext: (username: string, salt: string) => void }, { error: string, loading: boolean }> { username_input: HTMLInputElement; constructor() { super(); this.state = { error: undefined, loading: false } } async onClick() { this.setState({ loading: true }); try { let res = await fetch("/api/user/login?type=username&username=" + this.username_input.value, { method: "POST" }).then(e => { if (e.status !== 200) throw new Error(e.statusText) return e.json() }).then(data => { if (data.error) { return Promise.reject(new Error(data.error)) } return data; }) let salt = res.salt; this.props.onNext(this.username_input.value, salt); } catch (err) { this.setState({ error: err.message }); } this.setState({ loading: false }); } render() { if (this.state.loading) return return
{ let k = e.keyCode | e.which; if (k === 13) this.onClick(); this.setState({ error: undefined }) }} type="text" value={this.username_input ? this.username_input.value : this.props.username} autofocus ref={elm => elm ? this.username_input = elm : undefined} /> {this.state.error ?
{this.state.error}
: undefined}
} } enum TFATypes { OTC, BACKUP_CODE, YUBI_KEY, APP_ALLOW } interface TwoFactors { id: string; name: string; type: TFATypes; } class Password extends Component<{ username: string, salt: string, onNext: (login: Token, special: Token, tfa: TwoFactors[]) => void }, { error: string, loading: boolean }> { password_input: HTMLInputElement; constructor() { super(); this.state = { error: undefined, loading: false } } async onClick() { this.setState({ loading: true }); try { let pw = sha(this.props.salt + this.password_input.value); let { login, special, tfa } = await fetch("/api/user/login?type=password", { method: "POST", body: JSON.stringify({ username: this.props.username, password: pw }), headers: { 'content-type': 'application/json' }, }).then(e => { if (e.status !== 200) throw new Error(e.statusText) return e.json() }).then(data => { if (data.error) { return Promise.reject(new Error(data.error)) } return data; }) this.props.onNext(login, special, tfa); } catch (err) { this.setState({ error: err.messagae }); } this.setState({ loading: false }); } render() { if (this.state.loading) return return
{ let k = e.keyCode | e.which; if (k === 13) this.onClick(); this.setState({ error: undefined }) }} type="password" ref={(elm: HTMLInputElement) => { if (elm) { this.password_input = elm setTimeout(() => elm.focus(), 200) // elm.focus(); } } } /> {this.state.error ?
{this.state.error}
: undefined}
} } class TwoFactor extends Component<{ twofactors: TwoFactors[], next: (id: string, type: TFATypes) => void }, {}> { render() { let tfs = this.props.twofactors.map(fac => { let name: string; switch (fac.type) { case TFATypes.OTC: name = "Authenticator" break; case TFATypes.BACKUP_CODE: name = "Backup code"; break; case TFATypes.APP_ALLOW: name = "Use App: %s" break; case TFATypes.YUBI_KEY: name = "Use Yubikey: %s" break; } name = name.replace("%s", fac.name ? fac.name : ""); return
  • { console.log("Click on Solution") this.props.next(fac.id, fac.type) }}> {name}
  • }) return

    Select one

      {tfs}
    } } // class TFA_YubiKey extends Component<{ id: string, login: Token, special: Token, next: (login: Token, special: Token) => void }, {}> { // render() { // } // } enum Page { username, password, twofactor, yubikey } interface Token { token: string; expires: string; } async function apiRequest(endpoint: string, method: "GET" | "POST" | "DELETE" | "PUT" = "GET", body: string = undefined) { return fetch(endpoint, { method, body, credentials: "same-origin", headers: { "content-type": "application/json" } }).then(e => { if (e.status !== 200) throw new Error(e.statusText) return e.json() }).then(data => { if (data.error) { return Promise.reject(new Error(data.error)) } return data; }) } class App extends Component<{}, { page: Page, username: string, salt: string, twofactor: TwoFactors[], twofactor_id: string }> { login: Token; special: Token; constructor() { super(); this.state = { page: Page.username, username: getCookie("username"), salt: undefined, twofactor: [], twofactor_id: null } } setCookies() { setCookie("login", this.login.token, new Date(this.login.expires).toUTCString()); setCookie("special", this.special.token, new Date(this.special.expires).toUTCString()); } finish() { this.setCookies(); let d = new Date() d.setTime(d.getTime() + (30 * 24 * 60 * 60 * 1000)); //Keep the username 30 days setCookie("username", this.state.username, d.toUTCString()); let url = new URL(window.location.href); let state = url.searchParams.get("state") let red = "/" if (state) { let base64 = url.searchParams.get("base64") if (base64) red = atob(state) else red = state } window.location.href = red; } render() { let cont; switch (this.state.page) { case Page.username: cont = { this.setState({ username, salt, page: Page.password }) localStorage.setItem("username", username); }} /> break; case Page.password: cont = { this.login = login; this.special = special; this.setCookies(); if (!twofactor) { this.finish(); } else { this.setState({ twofactor, page: Page.twofactor }); } }} /> break; case Page.twofactor: cont = { if (type === TFATypes.YUBI_KEY) { let { request } = await apiRequest("/api/user/twofactor/yubikey", "GET"); console.log(request); (window as any).u2f.sign(request.appId, [request.challenge], [request], async (response) => { let res = await apiRequest("/api/user/twofactor/yubikey", "PUT", JSON.stringify({ response })); if (res.success) { this.login.expires = res.login_exp; this.special.expires = res.special_exp; this.finish(); } }) } }} /> break; // case Page.yubikey: // cont = { // this.login = login; // this.special = special; // this.finish() // }} /> // break; } return

    Login

    {cont}

    Powered by {appname}

    } } document.addEventListener('DOMContentLoaded', function () { render(, document.body.querySelector("#content")) }, false)