Add JRPC API, reworked Login and User pages

This commit is contained in:
Fabian Stamm
2023-04-14 15:13:53 +02:00
parent 922ed1e813
commit e1164eb05b
99 changed files with 4570 additions and 5471 deletions

View File

@ -1,124 +1,34 @@
<script>
<script lang="ts">
import {} from "flowbite-svelte";
import { LoginState } from "@hibas123/openauth-internalapi";
import Theme from "../../components/theme";
import loginState from "./state";
import HoveringContentBox from "../../components/HoveringContentBox.svelte";
import Api from "./api.ts";
import Credentials from "./Credentials.svelte";
import Redirect from "./Redirect.svelte";
import Twofactor from "./Twofactor.svelte";
import { onMount } from "svelte";
import Username from "./Username.svelte";
import Password from "./Password.svelte";
import Success from "./Success.svelte";
import TwoFactor from "./TwoFactor.svelte";
const appname = "OpenAuth";
const states = {
credentials: 1,
twofactor: 3,
redirect: 4,
};
let username = Api.getUsername();
let password = "";
let loading = false;
let state = states.credentials;
function getButtonText(state) {
switch (state) {
case states.username:
return "Next";
case states.password:
return "Login";
default:
return "";
}
}
$: btnText = getButtonText(state);
let error;
// window.addEventListener("popstate", () => {
// state = history.state;
// })
function LoadRedirect() {
state = states.redirect;
}
function Loading() {
state = states.loading;
}
let salt;
async function buttonClick() {
if (state === states.username) {
Loading();
let res = await Api.setUsername(username);
if (res.error) {
error = res.error;
LoadUsername();
} else {
LoadPassword();
}
} else if (state === states.password) {
Loading();
let res = await Api.setPassword(password);
if (res.error) {
error = res.error;
LoadPassword();
} else {
if (res.tfa) {
// TODO: Make TwoFactor UI/-s
} else {
LoadRedirect();
}
}
btnText = "Error";
}
}
function startRedirect() {
state = states.redirect;
// Show message to User and then redirect
setTimeout(() => Api.finish(), 2000);
}
function afterCredentials() {
Object.keys(Api); // Some weird bug needs this???
if (Api.twofactor) {
state = states.twofactor;
} else {
startRedirect();
}
}
function afterTwoFactor() {
startRedirect();
}
const { state } = loginState;
</script>
<style>
footer {
text-align: center;
position: absolute;
bottom: 0;
left: 0;
right: 0;
}
</style>
<Theme>
<HoveringContentBox title="Login" {loading}>
<HoveringContentBox title="Login" loading={$state.loading}>
<form action="JavaScript:void(0)">
{#if state === states.redirect}
<Redirect />
{:else if state === states.credentials}
<Credentials next={afterCredentials} setLoading={(s) => (loading = s)} />
{:else if state === states.twofactor}
<Twofactor finish={afterTwoFactor} setLoading={(s) => (loading = s)} />
{#if $state.success}
<Success />
{:else if !$state.username}
<Username on:username={(evt) => loginState.setUsername(evt.detail)} />
{:else if !$state.password}
<Password
username={$state.username}
on:password={(evt) => loginState.setPassword(evt.detail)}
/>
{:else if $state.requireTwoFactor.length > 0}
<TwoFactor />
{/if}
</form>
</HoveringContentBox>
<footer>
<p>Powered by {appname}</p>
</footer>
</Theme>

View File

@ -1,84 +0,0 @@
<script>
import Api from "./api.ts";
let error;
let password = "";
let username = Api.getUsername();
const states = {
username: 1,
password: 2
};
let state = states.username;
let salt;
export let setLoading;
export let next;
async function buttonClick() {
setLoading(true);
if (state === states.username) {
let res = await Api.setUsername(username);
if (res.error) {
error = res.error;
} else {
state = states.password;
error = undefined;
}
} else if (state === states.password) {
let res = await Api.setPassword(password);
if (res.error) {
error = res.error;
} else {
error = undefined;
next();
}
}
setLoading(false);
}
</script>
<style>
.error {
color: var(--error);
padding: 4px;
}
.wide-button {
width: 100%;
margin: 0;
}
</style>
{#if state === states.username}
<h3>Enter your Username or your E-Mail Address</h3>
<div class="floating group">
<input
type="text"
autocomplete="username"
autofocus
bind:value={username} />
<span class="highlight" />
<span class="bar" />
<label>Username or E-Mail</label>
<div class="error" style={!error ? 'display: none;' : ''}>{error}</div>
</div>
{:else}
<h3>Enter password for {username}</h3>
<div class="floating group">
<input
type="password"
autocomplete="password"
autofocus
bind:value={password} />
<span class="highlight" />
<span class="bar" />
<label>Password</label>
<div class="error" style={!error ? 'display: none;' : ''}>{error}</div>
</div>
{/if}
<button class="btn btn-primary wide-button" on:click={buttonClick}>Next</button>

View File

@ -0,0 +1,16 @@
<script lang="ts">
import loginState from "./state";
let { state } = loginState;
</script>
{#if $state.error}
<div class="error">{$state.error}</div>
{/if}
<style>
.error {
color: var(--error);
padding: 4px;
}
</style>

View File

@ -0,0 +1,30 @@
<script lang="ts">
import { createEventDispatcher } from "svelte";
import Error from "./Error.svelte";
let password: string = "";
export let username: string;
const dispatch = createEventDispatcher();
</script>
<h3>Enter the password for {username}</h3>
<div class="floating group">
<!-- svelte-ignore a11y-autofocus -->
<input
id="password"
type="password"
autocomplete="password"
autofocus
bind:value={password}
/>
<span class="highlight" />
<span class="bar" />
<label for="password">Password</label>
<Error />
</div>
<button
class="btn btn-primary btn-wide"
on:click={() => dispatch("password", password)}>Next</button
>

View File

@ -1,8 +1,8 @@
<script>
import Cleave from "cleave.js";
import { onMount } from "svelte";
import Error from "../Error.svelte";
export let error;
// export let label;
export let value;
export let length = 6;
@ -17,17 +17,11 @@
});
</script>
<style>
.error {
color: var(--error);
margin-top: 4px;
}
</style>
<div class="floating group">
<input id="noasidhglk" bind:this={input} autofocus bind:value />
<input id="code-input" bind:this={input} autofocus bind:value />
<span class="highlight" />
<span class="bar" />
<label for="noasidhglk">Code</label>
<div class="error" style={!error ? 'display: none;' : ''}>{error}</div>
<label for="code-input">Code</label>
<Error />
</div>

View File

@ -0,0 +1,21 @@
<script lang="ts">
import Error from "../Error.svelte";
import loginState from "../state";
import CodeInput from "./CodeInput.svelte";
export let id: string;
export let name: string;
let code: string = "";
function send() {
loginState.useTOTP(id, code);
}
</script>
<h3>TOTP {name}</h3>
<CodeInput bind:value={code} length={6} />
<div class="actions">
<button class="btn btn-primary btn-wide" on:click={send}> Send </button>
</div>

View File

@ -0,0 +1,28 @@
<script lang="ts">
import { onMount } from "svelte";
import Error from "../Error.svelte";
import loginState from "../state";
import { startAuthentication } from "@simplewebauthn/browser";
export let id: string;
async function doAuth() {
let challenge = await loginState.getWebAuthnChallenge(id);
try {
loginState.setLoading(true);
let result = await startAuthentication(JSON.parse(challenge));
await loginState.useWebAuthn(id, result);
} catch (e) {
loginState.setError(e.message);
return;
} finally {
loginState.setLoading(false);
}
}
onMount(() => {
doAuth();
});
</script>
<Error />

View File

@ -0,0 +1,114 @@
<script lang="ts">
import { createEventDispatcher } from "svelte";
import loginState from "./state";
import Icon from "./icons/Icon.svelte";
import { TFAType } from "@hibas123/openauth-internalapi";
import { onMount } from "svelte";
import Totp from "./TF/TOTP.svelte";
import Error from "./Error.svelte";
import WebAuthn from "./TF/WebAuthn.svelte";
let { state } = loginState;
const dispatch = createEventDispatcher();
let selected = undefined;
$: {
if ($state.requireTwoFactor?.length == 1) {
selected = $state.requireTwoFactor[0];
}
}
const typeIconMap = {
[TFAType.TOTP]: "Authenticator",
[TFAType.BACKUP_CODE]: "BackupCode",
[TFAType.WEBAUTHN]: "SecurityKey",
[TFAType.APP_ALLOW]: "AppPush",
};
</script>
{#if !selected}
<h3>Choose your 2FA method</h3>
<ul>
{#each $state.requireTwoFactor ?? [] as method}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<li on:click={() => (selected = method)}>
<div class="icon">
<Icon icon_name={typeIconMap[method.tfatype]} />
</div>
<div class="name">{method.name}</div>
</li>
{/each}
<Error />
</ul>
{:else}
{#if selected.tfatype == TFAType.TOTP}
<Totp id={selected.id} name={selected.name} />
{:else if selected.tfatype == TFAType.BACKUP_CODE}
backup
{:else if selected.tfatype == TFAType.WEBAUTHN}
<WebAuthn id={selected.id} />
{:else if selected.tfatype == TFAType.APP_ALLOW}
appallow
{:else}
<p>Unknown 2FA type</p>
{/if}
<p>
<a
class="to-list"
href="# "
on:click={(evt) => {
evt.preventDefault();
loginState.setError(undefined);
selected = undefined;
}}
>
Choose another Method
</a>
</p>
{/if}
<style>
ul {
list-style: none;
padding-inline-start: 0;
margin-bottom: 0;
}
li {
border-top: 1px grey solid;
padding: 1em;
cursor: pointer;
display: flex;
}
li:hover {
background-color: #e2e2e2;
}
li:first-child {
border-top: none !important;
}
.icon {
height: 1.5rem;
width: 1.5rem;
}
.name {
margin-left: 1rem;
line-height: 1.5rem;
font-size: 20px;
flex-grow: 1;
}
.to-list {
color: var(--primary);
text-decoration: none;
margin-right: 1rem;
}
</style>

View File

@ -1,104 +0,0 @@
<script>
import Api, { TFATypes } from "./api.ts";
import Icon from "./icons/Icon.svelte";
import OTCTwoFactor from "./twofactors/otc.svelte";
import PushTwoFactor from "./twofactors/push.svelte";
import U2FTwoFactor from "./twofactors/u2f.svelte";
const states = {
list: 1,
twofactor: 2
};
function getIcon(tf) {
switch (tf.type) {
case TFATypes.OTC:
return "Authenticator";
case TFATypes.BACKUP_CODE:
return "BackupCode";
case TFATypes.U2F:
return "SecurityKey";
case TFATypes.APP_ALLOW:
return "AppPush";
}
}
let twofactors = Api.twofactor.map(tf => {
return {
...tf,
icon: getIcon(tf)
};
});
let state = states.list;
let twofactor = undefined;
twofactor = twofactors[0];
$: console.log(twofactor);
function onFinish(res) {
if (res) finish();
else twofactor = undefined;
}
export let finish;
</script>
<style>
ul {
list-style: none;
padding-inline-start: 0;
margin-bottom: 0;
}
li {
border-top: 1px grey solid;
padding: 1em;
cursor: pointer;
}
li:first-child {
border-top: none !important;
}
.icon {
float: left;
height: 24px;
width: 24px;
}
.name {
margin-left: 48px;
line-height: 24px;
font-size: 20px;
}
</style>
<div>
{#if !twofactor}
<h3>Select your Authentication method:</h3>
<ul>
{#each twofactors as tf}
<li on:click={() => (twofactor = tf)}>
<div class="icon">
<Icon icon_name={tf.icon} />
</div>
<div class="name">{tf.name}</div>
</li>
{/each}
</ul>
{:else if twofactor.type === TFATypes.OTC}
<OTCTwoFactor id={twofactor.id} finish={onFinish} otc={true} />
{:else if twofactor.type === TFATypes.BACKUP_CODE}
<OTCTwoFactor id={twofactor.id} finish={onFinish} otc={false} />
{:else if twofactor.type === TFATypes.U2F}
<U2FTwoFactor id={twofactor.id} finish={onFinish} />
{:else if twofactor.type === TFATypes.APP_ALLOW}
<PushTwoFactor id={twofactor.id} finish={onFinish} />
{:else}
<div>Invalid TwoFactor Method!</div>
{/if}
</div>

View File

@ -0,0 +1,29 @@
<script lang="ts">
import { createEventDispatcher } from "svelte";
import Error from "./Error.svelte";
let username: string = "";
const dispatch = createEventDispatcher();
</script>
<h3>Enter your Username or your E-Mail Address</h3>
<div class="floating group">
<!-- svelte-ignore a11y-autofocus -->
<input
id="username"
type="text"
autocomplete="username"
autofocus
bind:value={username}
/>
<span class="highlight" />
<span class="bar" />
<label for="username">Username or E-Mail</label>
<Error />
</div>
<button
class="btn btn-primary btn-wide"
on:click={() => dispatch("username", username)}>Next</button
>

View File

@ -1,182 +0,0 @@
import request from "../../helper/request";
import sha from "../../helper/sha512";
import { setCookie, getCookie } from "../../helper/cookie";
export interface TwoFactor {
id: string;
name: string;
type: TFATypes;
}
export enum TFATypes {
OTC,
BACKUP_CODE,
U2F,
APP_ALLOW,
}
// const Api = {
// // twofactor: [{
// // id: "1",
// // name: "Backup Codes",
// // type: TFATypes.BACKUP_CODE
// // }, {
// // id: "2",
// // name: "YubiKey",
// // type: TFATypes.U2F
// // }, {
// // id: "3",
// // name: "Authenticator",
// // type: TFATypes.OTC
// // }] as TwoFactor[],
// }
export interface IToken {
token: string;
expires: string;
}
function makeid(length) {
var result = "";
var characters =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
var charactersLength = characters.length;
for (var i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
export default class Api {
static salt: string;
static login: IToken;
static special: IToken;
static username: string;
static twofactor: any[];
static getUsername() {
return this.username || getCookie("username");
}
static async setUsername(
username: string
): Promise<{ error: string | undefined }> {
return request(
"/api/user/login",
{
type: "username",
username,
},
"POST"
)
.then((res) => {
this.salt = res.salt;
this.username = username;
return {
error: undefined,
};
})
.catch((err) => {
let error = err.message;
return { error };
});
}
static async setPassword(
password: string
): Promise<{ error: string | undefined; twofactor?: any }> {
const date = new Date().valueOf();
let pw = sha(sha(this.salt + password) + date.toString());
return request(
"/api/user/login",
{
type: "password",
},
"POST",
{
username: this.username,
password: pw,
date,
}
)
.then(({ login, special, tfa }) => {
this.login = login;
this.special = special;
if (tfa && Array.isArray(tfa) && tfa.length > 0)
this.twofactor = tfa;
else this.twofactor = undefined;
return {
error: undefined,
};
})
.catch((err) => {
let error = err.message;
return { error };
});
}
static gettok() {
return {
login: this.login.token,
special: this.special.token,
};
}
static async sendBackup(id: string, code: string) {
return request("/api/user/twofactor/backup", this.gettok(), "PUT", {
code,
id,
})
.then(({ login_exp, special_exp }) => {
this.login.expires = login_exp;
this.special.expires = special_exp;
return {};
})
.catch((err) => ({ error: err.message }));
}
static async sendOTC(id: string, code: string) {
return request("/api/user/twofactor/otc", this.gettok(), "PUT", {
code,
id,
})
.then(({ login_exp, special_exp }) => {
this.login.expires = login_exp;
this.special.expires = special_exp;
return {};
})
.catch((error) => ({ error: error.message }));
}
static finish() {
let d = new Date();
d.setTime(d.getTime() + 30 * 24 * 60 * 60 * 1000); //Keep the username 30 days
setCookie("username", this.username, d.toUTCString());
setCookie(
"login",
this.login.token,
new Date(this.login.expires).toUTCString()
);
setCookie(
"special",
this.special.token,
new Date(this.special.expires).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;
}
setTimeout(() => (window.location.href = red), 200);
}
}

View File

@ -0,0 +1,183 @@
import type { LoginState } from "@hibas123/openauth-internalapi";
import { derived, get, writable } from "svelte/store";
import InternalAPI from "../../helper/api";
import sha from "../../helper/sha512";
interface LocalLoginState extends LoginState {
loading: boolean;
error?: string;
username?: string;
}
class LoginStore {
state = writable<LocalLoginState>({
username: undefined,
password: false,
passwordSalt: undefined,
requireTwoFactor: [],
success: false,
loading: true,
error: undefined
})
isFinished = derived(this.state, $state => $state.success);
constructor() {
this.state.subscribe((state) => {
if (state.success) {
setTimeout(() => {
this.finish();
}, 2000);
}
})
this.getState();
}
setLoading(loading: boolean) {
this.state.update(current => ({
...current,
loading,
error: loading ? undefined : current.error,
}));
}
setError(error: string) {
this.state.update(current => ({
...current,
error,
}));
}
async getState() {
try {
this.setLoading(true);
let state = await InternalAPI.Login.GetState();
this.state.update(current => ({
...current,
...state,
}));
} catch (err) {
this.setError(err.message);
} finally {
this.setLoading(false);
}
}
async setUsername(username: string) {
try {
this.setLoading(true);
let state = await InternalAPI.Login.Start(username);
this.state.update(current => ({
...current,
...state,
username
}));
} catch (err) {
this.setError(err.message);
} finally {
this.setLoading(false);
}
}
async setPassword(password: string) {
try {
this.setLoading(true);
const date = new Date().valueOf();
let salt = get(this.state).passwordSalt
let pw = sha(sha(salt + password) + date.toString());
let state = await InternalAPI.Login.UsePassword(pw, date);
this.state.update(current => ({
...current,
...state,
}));
} catch (err) {
this.setError(err.message);
} finally {
this.setLoading(false);
}
}
async useTOTP(id: string, code: string) {
try {
this.setLoading(true);
let state = await InternalAPI.Login.UseTOTP(id, code);
this.state.update(current => ({
...current,
...state,
}));
} catch (err) {
this.setError(err.message);
} finally {
this.setLoading(false);
}
}
async useBackupCode(id: string, code: string) {
try {
this.setLoading(true);
let state = await InternalAPI.Login.UseBackupCode(id, code);
this.state.update(current => ({
...current,
...state,
}));
} catch (err) {
this.setError(err.message);
} finally {
this.setLoading(false);
}
}
async getWebAuthnChallenge(id: string) {
try {
this.setLoading(true);
let challenge = await InternalAPI.Login.GetWebAuthnChallenge(id);
return challenge;
} catch (err) {
this.setError(err.message);
} finally {
this.setLoading(false);
}
}
async useWebAuthn(id: string, response: any) {
try {
this.setLoading(true);
let state = await InternalAPI.Login.UseWebAuthn(id, JSON.stringify(response));
this.state.update(current => ({
...current,
...state,
}));
} catch (err) {
this.setError(err.message);
} finally {
this.setLoading(false);
}
}
async finish() {
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;
}
setTimeout(() => (window.location.href = red), 200);
}
}
const loginState = new LoginStore();
export default loginState;

View File

@ -1,50 +0,0 @@
<script>
import ToList from "./toList.svelte";
import Api from "../api.ts";
import CodeInput from "./codeInput.svelte";
let error = "";
let code = "";
export let finish;
export let id;
export let otc = false;
let title = otc ? "One Time Code (OTC)" : "Backup Code";
let length = otc ? 6 : 8;
async function sendCode() {
let c = code.replace(/\s+/g, "");
if (c.length < length) {
error = `Code must be ${length} digits long!`;
} else {
error = "";
let res;
if (otc) res = await Api.sendOTC(id, c);
else res = await Api.sendBackup(id, c);
if (res.error) error = res.error;
else finish(true);
}
}
</script>
<style>
.actions {
display: flex;
align-items: center;
}
.btn-next {
margin: 0;
margin-left: auto;
min-width: 80px;
}
</style>
<h3>{title}</h3>
<CodeInput bind:value={code} label="Code" {error} {length} />
<div class="actions">
<ToList {finish} />
<button class="btn btn-primary btn-next" on:click={sendCode}> Send </button>
</div>

View File

@ -1,389 +0,0 @@
<script>
import ToList from "./toList.svelte";
let error = "";
let code = "";
export let device = "Handy01";
// export let deviceId = "";
export let finish;
async function requestPush() {
// Push Request
}
</script>
<style>
.error {
color: var(--error);
}
.windows8 {
position: relative;
width: 56px;
height: 56px;
margin: 2rem auto;
}
.windows8 .wBall {
position: absolute;
width: 53px;
height: 53px;
opacity: 0;
transform: rotate(225deg);
-o-transform: rotate(225deg);
-ms-transform: rotate(225deg);
-webkit-transform: rotate(225deg);
-moz-transform: rotate(225deg);
animation: orbit 5.7425s infinite;
-o-animation: orbit 5.7425s infinite;
-ms-animation: orbit 5.7425s infinite;
-webkit-animation: orbit 5.7425s infinite;
-moz-animation: orbit 5.7425s infinite;
}
.windows8 .wBall .wInnerBall {
position: absolute;
width: 7px;
height: 7px;
background: rgb(0, 140, 255);
left: 0px;
top: 0px;
border-radius: 7px;
}
.windows8 #wBall_1 {
animation-delay: 1.256s;
-o-animation-delay: 1.256s;
-ms-animation-delay: 1.256s;
-webkit-animation-delay: 1.256s;
-moz-animation-delay: 1.256s;
}
.windows8 #wBall_2 {
animation-delay: 0.243s;
-o-animation-delay: 0.243s;
-ms-animation-delay: 0.243s;
-webkit-animation-delay: 0.243s;
-moz-animation-delay: 0.243s;
}
.windows8 #wBall_3 {
animation-delay: 0.5065s;
-o-animation-delay: 0.5065s;
-ms-animation-delay: 0.5065s;
-webkit-animation-delay: 0.5065s;
-moz-animation-delay: 0.5065s;
}
.windows8 #wBall_4 {
animation-delay: 0.7495s;
-o-animation-delay: 0.7495s;
-ms-animation-delay: 0.7495s;
-webkit-animation-delay: 0.7495s;
-moz-animation-delay: 0.7495s;
}
.windows8 #wBall_5 {
animation-delay: 1.003s;
-o-animation-delay: 1.003s;
-ms-animation-delay: 1.003s;
-webkit-animation-delay: 1.003s;
-moz-animation-delay: 1.003s;
}
@keyframes orbit {
0% {
opacity: 1;
z-index: 99;
transform: rotate(180deg);
animation-timing-function: ease-out;
}
7% {
opacity: 1;
transform: rotate(300deg);
animation-timing-function: linear;
origin: 0%;
}
30% {
opacity: 1;
transform: rotate(410deg);
animation-timing-function: ease-in-out;
origin: 7%;
}
39% {
opacity: 1;
transform: rotate(645deg);
animation-timing-function: linear;
origin: 30%;
}
70% {
opacity: 1;
transform: rotate(770deg);
animation-timing-function: ease-out;
origin: 39%;
}
75% {
opacity: 1;
transform: rotate(900deg);
animation-timing-function: ease-out;
origin: 70%;
}
76% {
opacity: 0;
transform: rotate(900deg);
}
100% {
opacity: 0;
transform: rotate(900deg);
}
}
@-o-keyframes orbit {
0% {
opacity: 1;
z-index: 99;
-o-transform: rotate(180deg);
-o-animation-timing-function: ease-out;
}
7% {
opacity: 1;
-o-transform: rotate(300deg);
-o-animation-timing-function: linear;
-o-origin: 0%;
}
30% {
opacity: 1;
-o-transform: rotate(410deg);
-o-animation-timing-function: ease-in-out;
-o-origin: 7%;
}
39% {
opacity: 1;
-o-transform: rotate(645deg);
-o-animation-timing-function: linear;
-o-origin: 30%;
}
70% {
opacity: 1;
-o-transform: rotate(770deg);
-o-animation-timing-function: ease-out;
-o-origin: 39%;
}
75% {
opacity: 1;
-o-transform: rotate(900deg);
-o-animation-timing-function: ease-out;
-o-origin: 70%;
}
76% {
opacity: 0;
-o-transform: rotate(900deg);
}
100% {
opacity: 0;
-o-transform: rotate(900deg);
}
}
@-ms-keyframes orbit {
0% {
opacity: 1;
z-index: 99;
-ms-transform: rotate(180deg);
-ms-animation-timing-function: ease-out;
}
7% {
opacity: 1;
-ms-transform: rotate(300deg);
-ms-animation-timing-function: linear;
-ms-origin: 0%;
}
30% {
opacity: 1;
-ms-transform: rotate(410deg);
-ms-animation-timing-function: ease-in-out;
-ms-origin: 7%;
}
39% {
opacity: 1;
-ms-transform: rotate(645deg);
-ms-animation-timing-function: linear;
-ms-origin: 30%;
}
70% {
opacity: 1;
-ms-transform: rotate(770deg);
-ms-animation-timing-function: ease-out;
-ms-origin: 39%;
}
75% {
opacity: 1;
-ms-transform: rotate(900deg);
-ms-animation-timing-function: ease-out;
-ms-origin: 70%;
}
76% {
opacity: 0;
-ms-transform: rotate(900deg);
}
100% {
opacity: 0;
-ms-transform: rotate(900deg);
}
}
@-webkit-keyframes orbit {
0% {
opacity: 1;
z-index: 99;
-webkit-transform: rotate(180deg);
-webkit-animation-timing-function: ease-out;
}
7% {
opacity: 1;
-webkit-transform: rotate(300deg);
-webkit-animation-timing-function: linear;
-webkit-origin: 0%;
}
30% {
opacity: 1;
-webkit-transform: rotate(410deg);
-webkit-animation-timing-function: ease-in-out;
-webkit-origin: 7%;
}
39% {
opacity: 1;
-webkit-transform: rotate(645deg);
-webkit-animation-timing-function: linear;
-webkit-origin: 30%;
}
70% {
opacity: 1;
-webkit-transform: rotate(770deg);
-webkit-animation-timing-function: ease-out;
-webkit-origin: 39%;
}
75% {
opacity: 1;
-webkit-transform: rotate(900deg);
-webkit-animation-timing-function: ease-out;
-webkit-origin: 70%;
}
76% {
opacity: 0;
-webkit-transform: rotate(900deg);
}
100% {
opacity: 0;
-webkit-transform: rotate(900deg);
}
}
@-moz-keyframes orbit {
0% {
opacity: 1;
z-index: 99;
-moz-transform: rotate(180deg);
-moz-animation-timing-function: ease-out;
}
7% {
opacity: 1;
-moz-transform: rotate(300deg);
-moz-animation-timing-function: linear;
-moz-origin: 0%;
}
30% {
opacity: 1;
-moz-transform: rotate(410deg);
-moz-animation-timing-function: ease-in-out;
-moz-origin: 7%;
}
39% {
opacity: 1;
-moz-transform: rotate(645deg);
-moz-animation-timing-function: linear;
-moz-origin: 30%;
}
70% {
opacity: 1;
-moz-transform: rotate(770deg);
-moz-animation-timing-function: ease-out;
-moz-origin: 39%;
}
75% {
opacity: 1;
-moz-transform: rotate(900deg);
-moz-animation-timing-function: ease-out;
-moz-origin: 70%;
}
76% {
opacity: 0;
-moz-transform: rotate(900deg);
}
100% {
opacity: 0;
-moz-transform: rotate(900deg);
}
}
</style>
<h3>SMS</h3>
<p>A code was sent to your Device <b>{device}</b></p>
<div class="windows8">
<div class="wBall" id="wBall_1">
<div class="wInnerBall" />
</div>
<div class="wBall" id="wBall_2">
<div class="wInnerBall" />
</div>
<div class="wBall" id="wBall_3">
<div class="wInnerBall" />
</div>
<div class="wBall" id="wBall_4">
<div class="wInnerBall" />
</div>
<div class="wBall" id="wBall_5">
<div class="wInnerBall" />
</div>
</div>
<div class="error">{error}</div>
<ToList {finish} />

View File

@ -1,49 +0,0 @@
<script>
import ToList from "./toList.svelte";
const states = {
approve: 1,
enter: 2,
};
let state = states.approve;
let error = "";
let code = "";
export let number = "+4915...320";
//export let finish;
function validateCode() {}
function sendCode() {
// Send request to Server
state = states.enter;
//finish()
}
</script>
<style>
:root {
--error: red;
}
.error {
color: var(--error);
}
</style>
<h3>SMS</h3>
{#if state === states.approve}
<p>Send SMS to {number}</p>
<button class="btn btn-primary" on:click={sendCode}>Send</button>
{:else}
<p>A code was sent to you. Please enter</p>
<input type="number" placeholder="Code" bind:value={code} />
<button class="btn btn-primary" on:click={validateCode}>Send</button>
<br />
<a href="# " on:click|preventDefault={() => (state = states.approve)}>
Not received?
</a>
{/if}
<div class="error">{error}</div>
<ToList {finish} />

View File

@ -1,17 +0,0 @@
<script>
export let finish = () => {};
</script>
<style>
a {
color: var(--primary);
text-decoration: none;
margin-right: 1rem;
}
</style>
<p>
<a href="# " on:click={evt => evt.preventDefault() || finish(false)}>
Choose another Method
</a>
</p>

View File

@ -1,69 +0,0 @@
<script>
import ToList from "./toList.svelte";
export let finish;
const states = {
getChallenge: 0,
requestUser: 1,
sendChallenge: 2,
error: 3
};
let state = states.getChallenge;
let error = "";
const onError = err => {
state = states.error;
error = err.message;
};
let challenge;
async function requestUser() {
state = states.requestUser;
let res = await window.navigator.credentials.get({
publicKey: challenge
});
state = states.sendChallenge();
let r = res.response;
let data = encode({
authenticatorData: r.authenticatorData,
clientDataJSON: r.clientDataJSON,
signature: r.signature,
userHandle: r.userHandle
});
let { success } = fetch("https://localhost:8444/auth", {
body: data,
method: "POST"
}).then(res => res.json());
if (success) {
finish(true);
}
}
async function getChallenge() {
state = states.getChallenge;
challenge = await fetch("https://localhost:8444/auth")
.then(res => res.arrayBuffer())
.then(data => decode(MessagePack.Buffer.from(data)));
requestUser().catch(onError);
}
getChallenge().catch(onError);
</script>
<style>
:root {
--error: red;
}
.error {
color: var(--error);
}
</style>
<h3>U2F Security Key</h3>
<h4>This Method is currently not supported. Please choose another one!</h4>
<ToList {finish} />