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 );