Adding password salting

This commit is contained in:
Fabian Stamm
2020-02-07 19:30:52 +01:00
parent 7d10fdae30
commit f2e9de9423
13 changed files with 1041 additions and 904 deletions

View File

@ -1,206 +1,199 @@
<script>
import Api from "./api";
import Credentials from "./Credentials.svelte";
import Redirect from "./Redirect.svelte";
import Twofactor from "./Twofactor.svelte";
import Api from "./api.ts";
import Credentials from "./Credentials.svelte";
import Redirect from "./Redirect.svelte";
import Twofactor from "./Twofactor.svelte";
const appname = "OpenAuth";
const appname = "OpenAuth";
const states = {
credentials: 1,
twofactor: 3,
redirect: 4
}
const states = {
credentials: 1,
twofactor: 3,
redirect: 4
};
let username = Api.getUsername();
let password = "";
let username = Api.getUsername();
let password = "";
let loading = false;
let state = states.credentials;
let loading = false;
let state = states.credentials;
function getButtonText(state) {
switch (state) {
case states.username:
return "Next";
case states.password:
return "Login";
default:
return "";
}
}
function getButtonText(state) {
switch (state) {
case states.username:
return "Next";
case states.password:
return "Login";
default:
return "";
}
}
$: btnText = getButtonText(state);
$: btnText = getButtonText(state);
let error;
let error;
// window.addEventListener("popstate", () => {
// state = history.state;
// })
// window.addEventListener("popstate", () => {
// state = history.state;
// })
function LoadRedirect() {
state = states.redirect;
}
function LoadRedirect() {
state = states.redirect;
}
function Loading() {
state = states.loading;
}
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;
let salt;
async function buttonClick() {
if (state === states.username) {
Loading();
let res = await Api.setUsername(username);
if (res.error) {
error = res.error;
LoadUsername();
} else {
startRedirect();
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 afterTwoFactor() {
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();
}
</script>
<style>
.card {
box-shadow: 0 14px 28px rgba(0, 0, 0, 0.25), 0 10px 10px rgba(0, 0, 0, 0.22);
background: #fafafa;
border-radius: 4px;
}
.form-container {
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 1rem;
box-sizing: border-box;
}
form {
width: 100%;
max-width: 380px;
margin: 0 auto;
box-shadow: 0 19px 38px rgba(0, 0, 0, 0.3), 0 15px 12px rgba(0, 0, 0, 0.22);
position: relative;
padding: 1px;
background-color: white !important;
margin-bottom: 40px;
}
.container {
overflow: hidden;
padding: 2em;
}
button {
margin-top: 16px;
}
footer {
text-align: center;
position: absolute;
bottom: 0;
left: 0;
right: 0;
}
.floating {
margin-top: 8px;
}
h3 {
font-weight: 200;
}
.title-container {
margin: -30px auto 0 auto;
max-width: 250px;
background-color: var(--primary);
color: white;
padding: 5px 20px;
}
.loading_container {
filter: blur(1px) opacity(50%);
}
.loader_container {
position: absolute;
display: flex;
flex-direction: column;
justify-content: center;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 2;
}
</style>
<div class="form-container">
<form action="JavaScript:void(0)" class="card">
<div class="card title-container">
<h1>Login</h1>
</div>
{#if loading}
<form action="JavaScript:void(0)" class="card">
<div class="card title-container">
<h1>Login</h1>
</div>
{#if loading}
<div class="loader_container">
<div class="loader_box">
<div class="loader"></div>
</div>
<div class="loader_box">
<div class="loader" />
</div>
</div>
{/if}
<div class="container" class:loading_container={loading}>
{#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}
<div class="container" class:loading_container={loading}>
{#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}
</div>
</form>
</div>
</form>
</div>
<footer>
<p>Powered by {appname}</p>
<p>Powered by {appname}</p>
</footer>
<style>
.card {
box-shadow: 0 14px 28px rgba(0, 0, 0, 0.25), 0 10px 10px rgba(0, 0, 0, 0.22);
background: #fafafa;
border-radius: 4px;
}
.form-container {
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 1rem;
box-sizing: border-box;
}
form {
width: 100%;
max-width: 380px;
margin: 0 auto;
box-shadow: 0 19px 38px rgba(0, 0, 0, 0.30), 0 15px 12px rgba(0, 0, 0, 0.22);
position: relative;
padding: 1px;
background-color: white !important;
margin-bottom: 40px;
}
.container {
overflow: hidden;
padding: 2em;
}
button {
margin-top: 16px;
}
footer {
text-align: center;
position: absolute;
bottom: 0;
left: 0;
right: 0;
}
.floating {
margin-top: 8px;
}
h3 {
font-weight: 200;
}
.title-container {
margin: -30px auto 0 auto;
max-width: 250px;
background-color: var(--primary);
color: white;
padding: 5px 20px;
}
.loading_container {
filter: blur(1px) opacity(50%);
}
.loader_container {
position: absolute;
display: flex;
flex-direction: column;
justify-content: center;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 2;
}
</style>

View File

@ -1,79 +1,85 @@
<script>
import Api from "./api";
import Api from "./api.ts";
let error;
let password = "";
let username = Api.getUsername();
let error;
let password = "";
let username = Api.getUsername();
const states = {
username: 1,
password: 2
};
const states = {
username: 1,
password: 2
};
let state = states.username;
let state = states.username;
let salt;
let salt;
export let setLoading;
export let next;
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();
}
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);
}
setLoading(false);
}
</script>
<style>
.error {
color: var(--error);
padding: 4px;
}
.error {
color: var(--error);
padding: 4px;
}
.btn {
background-color: var(--primary);
width: 100%;
margin: 0;
}
.btn {
background-color: var(--primary);
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>
<span class="bar"></span>
<label>Username or E-Mail</label>
<div class="error" style={!error ? "display: none;" : "" }>{error}</div>
</div>
<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>
<span class="bar"></span>
<label>Password</label>
<div class="error" style={!error ? "display: none;" : "" }>{error}</div>
</div>
<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" on:click={buttonClick}>
Next
</button>
<button class="btn" on:click={buttonClick}>Next</button>

View File

@ -1,112 +1,104 @@
<script>
import Api, {
TFATypes
} from "./api";
import Icon from "./icons/Icon.svelte";
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";
import OTCTwoFactor from "./twofactors/otc.svelte";
import PushTwoFactor from "./twofactors/push.svelte";
import U2FTwoFactor from "./twofactors/u2f.svelte";
const states = {
list: 1,
twofactor: 2
}
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"
}
}
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 twofactors = Api.twofactor.map(tf => {
return {
...tf,
icon: getIcon(tf)
};
});
let state = states.list;
let state = states.list;
let twofactor = undefined;
twofactor = twofactors[0];
$: console.log(twofactor)
let twofactor = undefined;
twofactor = twofactors[0];
$: console.log(twofactor);
function onFinish(res) {
if (res)
finish()
else
twofactor = undefined;
}
function onFinish(res) {
if (res) finish();
else twofactor = undefined;
}
export let finish;
export let finish;
</script>
<style>
ul {
list-style: none;
padding-inline-start: 0;
margin-bottom: 0;
}
ul {
list-style: none;
padding-inline-start: 0;
margin-bottom: 0;
}
li {
border-top: 1px grey solid;
padding: 1em;
cursor: pointer;
}
li {
border-top: 1px grey solid;
padding: 1em;
cursor: pointer;
}
li:first-child {
border-top: none !important;
}
li:first-child {
border-top: none !important;
}
.icon {
float: left;
height: 24px;
width: 24px;
}
.icon {
float: left;
height: 24px;
width: 24px;
}
.name {
margin-left: 48px;
line-height: 24px;
font-size: 20px;
}
.name {
margin-left: 48px;
line-height: 24px;
font-size: 20px;
}
</style>
<div>
{#if !twofactor}
<h3>Select your Authentication method:</h3>
<ul>
{#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>
<li on:click={() => (twofactor = tf)}>
<div class="icon">
<Icon icon_name={tf.icon} />
</div>
<div class="name">
{tf.name}
</div>
</li>
<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}
{/if}
</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>
</div>

View File

@ -18,24 +18,51 @@ export enum TFATypes {
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[],
getUsername() {
// 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");
},
async setUsername(username: string): Promise<{ error: string | undefined }> {
}
static async setUsername(username: string): Promise<{ error: string | undefined }> {
return request("/api/user/login", {
type: "username",
username
@ -49,15 +76,18 @@ const Api = {
let error = err.message;
return { error }
})
},
async setPassword(password: string): Promise<{ error: string | undefined, twofactor?: any }> {
let pw = sha(this.salt + password);
}
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
}
username: this.username,
password: pw,
date
}
).then(({
login,
special,
@ -80,28 +110,32 @@ const Api = {
let error = err.message;
return { error }
})
},
gettok() {
}
static gettok() {
return {
login: this.login.token,
special: this.special.token
}
},
async sendBackup(id: string, code: string) {
}
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 }));
},
async sendOTC(id: string, code: string) {
}
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 }))
},
finish() {
}
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());
@ -122,6 +156,4 @@ const Api = {
}
setTimeout(() => window.location.href = red, 200);
}
}
export default Api;
}

View File

@ -1,56 +1,53 @@
<script>
import ToList from "./toList.svelte";
import Api from "../api";
import CodeInput from "./codeInput.svelte"
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;
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;
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)
}
}
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;
}
.actions {
display: flex;
align-items: center;
}
.btn {
background-color: var(--primary);
margin: 0;
margin-left: auto;
min-width: 80px;
}
.btn {
background-color: var(--primary);
margin: 0;
margin-left: auto;
min-width: 80px;
}
</style>
<h3>{title}</h3>
<CodeInput bind:value={code} label="Code" error={error} length={length} />
<CodeInput bind:value={code} label="Code" {error} {length} />
<div class="actions">
<ToList {finish} />
<button class="btn" style="margin-left: auto" on:click={sendCode}>Send</button>
</div>
<ToList {finish} />
<button class="btn" style="margin-left: auto" on:click={sendCode}>
Send
</button>
</div>

View File

@ -1,46 +1,45 @@
<script>
export let open;
export let pages = []
export let open;
export let pages = [];
</script>
<style>
:root {
--rel-size: 0.75rem;
}
:root {
--rel-size: 0.75rem;
}
.container {
height: calc(var(--rel-size) * 3);
padding: var(--rel-size);
display: flex;
/* align-content: center; */
align-items: center;
/* justify-content: center; */
}
.container {
height: calc(var(--rel-size) * 3);
padding: var(--rel-size);
display: flex;
/* align-content: center; */
align-items: center;
/* justify-content: center; */
}
.icon {
/* float: left; */
width: calc(var(--rel-size) * 3);
height: calc(var(--rel-size) * 3);
}
.icon {
/* float: left; */
width: calc(var(--rel-size) * 3);
height: calc(var(--rel-size) * 3);
}
.icon > img {
width: calc(var(--rel-size) * 3);
height: calc(var(--rel-size) * 3);
}
.icon>img {
width: calc(var(--rel-size) * 3);
height: calc(var(--rel-size) * 3);
}
.title {
/* margin: auto; */
margin-left: var(--rel-size);
/* height: 100%; */
}
.title {
/* margin: auto; */
margin-left: var(--rel-size);
/* height: 100%; */
}
</style>
{#each pages as page}
<div class="container" on:click={() => open(page.id)}>
<div class="icon"><img src={page.icon} /></div>
<h3 class="title">
{page.title}
</h3>
</div>
{/each}
<div class="container" on:click={() => open(page.id)}>
<div class="icon">
<img src={page.icon} />
</div>
<h3 class="title">{page.title}</h3>
</div>
{/each}

View File

@ -1,166 +1,165 @@
<script>
import Box from "./Box.svelte";
import BoxItem from "./BoxItem.svelte";
import NextIcon from "./NextIcon.svelte";
import Box from "./Box.svelte";
import BoxItem from "./BoxItem.svelte";
import NextIcon from "./NextIcon.svelte";
import request from "../../request"
import request from "../../request.ts";
export let loading = false;
let error = undefined;
export let loading = false;
let error = undefined;
const genderMap = new Map();
genderMap.set(0, "None");
genderMap.set(1, "Male");
genderMap.set(2, "Female");
genderMap.set(3, "Other");
const genderMap = new Map();
genderMap.set(0, "None");
genderMap.set(1, "Male");
genderMap.set(2, "Female");
genderMap.set(3, "Other");
let name = "";
let gender = 0;
$: genderHuman = genderMap.get(gender) || "ERROR";
let birthday = undefined;
let name = "";
let gender = 0;
$: genderHuman = genderMap.get(gender) || "ERROR";
let birthday = undefined;
async function saveName() {
//TODO: implement
await load();
}
async function saveName() {
//TODO: implement
await load();
}
async function saveGender() {
//TODO: implement
await load();
}
async function saveGender() {
//TODO: implement
await load();
}
async function loadProfile() {
try {
let { user } = await request(
"/api/user/account",
{},
"GET",
undefined,
true
);
async function loadProfile() {
try {
let {
user
} = await request("/api/user/account", {}, "GET", undefined, true)
name = user.name;
// username = user.username;
gender = user.gender;
birthday = user.birthday
? new Date(user.birthday).toLocaleDateString()
: undefined;
} catch (err) {
console.error(err);
error = err.message;
}
}
name = user.name;
// username = user.username;
gender = user.gender;
birthday = user.birthday ? new Date(user.birthday).toLocaleDateString() : undefined;
} catch (err) {
console.error(err);
error = err.message;
}
}
let email = ["mail@fabianstamm.de", "fabian.stamm.koe@gmail.com"];
let phone = ["+1 1233 123123123", "+21 1233 123 123 1"];
async function loadContact() {}
let email = ["mail@fabianstamm.de", "fabian.stamm.koe@gmail.com"];
let phone = ["+1 1233 123123123", "+21 1233 123 123 1"];
async function load() {
loading = true;
await Promise.all([loadProfile(), loadContact()]);
loading = false;
}
async function loadContact() {
}
async function load() {
loading = true;
await Promise.all([
loadProfile(),
loadContact()
])
loading = false;
}
load();
load();
</script>
<style>
.btn {
background-color: var(--primary);
margin: auto 0;
margin-left: 1rem;
font-size: 1rem;
padding: 0 0.5rem;
}
.btn {
background-color: var(--primary);
margin: auto 0;
margin-left: 1rem;
font-size: 1rem;
padding: 0 0.5rem;
}
.floating {
margin-bottom: 0;
}
.floating {
margin-bottom: 0;
}
.input-container {
display: flex;
}
.input-container {
display: flex;
}
.input-container>*:first-child {
flex-grow: 1;
}
.input-container > *:first-child {
flex-grow: 1;
}
select {
background-color: unset;
border: 0;
border-radius: 0;
color: unset;
font-size: unset;
border-bottom: 1px solid #757575;
/* Firefox */
-moz-appearance: none;
/* Safari and Chrome */
-webkit-appearance: none;
appearance: none;
height: 100%;
width: 100%;
}
select {
background-color: unset;
border: 0;
border-radius: 0;
color: unset;
font-size: unset;
border-bottom: 1px solid #757575;
/* Firefox */
-moz-appearance: none;
/* Safari and Chrome */
-webkit-appearance: none;
appearance: none;
height: 100%;
width: 100%;
}
select>option {
background-color: unset;
}
select > option {
background-color: unset;
}
.select-wrapper {
position: relative;
}
.select-wrapper {
position: relative;
}
.select-wrapper::after {
content: ">";
display: block;
position: absolute;
right: 2rem;
top: 0;
bottom: 0;
width: 1rem;
transform: rotate(90deg) scaleY(2);
}
.select-wrapper::after {
content: ">";
display: block;
position: absolute;
right: 2rem;
top: 0;
bottom: 0;
width: 1rem;
transform: rotate(90deg) scaleY(2);
}
.error {
color: var(--error);
}
.error {
color: var(--error);
}
</style>
<Box>
<h1>Profile</h1>
{#if error}
<p class="error">{error}</p>
{/if}
<BoxItem name="Name" value={name}>
<div class="input-container">
<div class="floating group">
<input type="text" autocomplete="username" bind:value={name}>
<span class="highlight"></span>
<span class="bar"></span>
<label>Name</label>
</div>
<button class="btn" on:click={saveName}>Save</button>
<h1>Profile</h1>
{#if error}
<p class="error">{error}</p>
{/if}
<BoxItem name="Name" value={name}>
<div class="input-container">
<div class="floating group">
<input type="text" autocomplete="username" bind:value={name} />
<span class="highlight" />
<span class="bar" />
<label>Name</label>
</div>
</BoxItem>
<BoxItem name="Gender" value={genderHuman}>
<div class="input-container">
<div class="select-wrapper">
<select bind:value={gender}>
<option value={1}>Male</option>
<option value={2}>Female</option>
<option value={3}>Other</option>
</select>
</div>
<button class="btn" on:click={saveGender}>Save</button>
<button class="btn" on:click={saveName}>Save</button>
</div>
</BoxItem>
<BoxItem name="Gender" value={genderHuman}>
<div class="input-container">
<div class="select-wrapper">
<select bind:value={gender}>
<option value={1}>Male</option>
<option value={2}>Female</option>
<option value={3}>Other</option>
</select>
</div>
</BoxItem>
<BoxItem name="Birthday" value={birthday} />
<BoxItem name="Password" value="******" />
<button class="btn" on:click={saveGender}>Save</button>
</div>
</BoxItem>
<BoxItem name="Birthday" value={birthday} />
<BoxItem name="Password" value="******" />
</Box>
<Box>
<h1>Contact</h1>
<BoxItem name="E-Mail" value={email} />
<BoxItem name="Phone" value={phone} />
</Box>
<h1>Contact</h1>
<BoxItem name="E-Mail" value={email} />
<BoxItem name="Phone" value={phone} />
</Box>

View File

@ -1,60 +1,144 @@
<script context="module">
const TFATypes = new Map()
TFATypes.set(0, "Authenticator")
TFATypes.set(1, "Backup Codes")
TFATypes.set(2, "YubiKey")
TFATypes.set(3, "Push Notification")
const TFATypes = new Map();
TFATypes.set(0, "Authenticator");
TFATypes.set(1, "Backup Codes");
TFATypes.set(2, "YubiKey");
TFATypes.set(3, "Push Notification");
</script>
<script>
import Box from "./Box.svelte";
import BoxItem from "./BoxItem.svelte";
import NextIcon from "./NextIcon.svelte";
import request from "../../request";
import Box from "./Box.svelte";
import BoxItem from "./BoxItem.svelte";
import NextIcon from "./NextIcon.svelte";
import request from "../../request.ts";
export let loading = false;
export let loading = false;
let twofactor = [];
let twofactor = [];
async function deleteTFA(id) {
let res = await request("/api/user/twofactor/" + id, undefined, "DELETE", undefined, true);
loadTwoFactor();
}
async function deleteTFA(id) {
let res = await request(
"/api/user/twofactor/" + id,
undefined,
"DELETE",
undefined,
true
);
loadTwoFactor();
}
async function loadTwoFactor() {
let res = await request("/api/user/twofactor", undefined, undefined, undefined, true);
twofactor = res.methods;
}
async function loadTwoFactor() {
let res = await request(
"/api/user/twofactor",
undefined,
undefined,
undefined,
true
);
twofactor = res.methods;
}
let token = [];
let token = [];
async function revoke(id) {
let res = await request(
"/api/user/token/" + id,
undefined,
"DELETE",
undefined,
true
);
loadToken();
}
async function revoke(id) {
let res = await request("/api/user/token/" + id, undefined, "DELETE", undefined, true);
loadToken();
}
async function loadToken() {
loading = true;
let res = await request(
"/api/user/token",
undefined,
undefined,
undefined,
true
);
token = res.token;
loading = false;
}
async function loadToken() {
loading = true;
let res = await request("/api/user/token", undefined, undefined, undefined, true);
token = res.token;
loading = false;
}
loadToken();
loadTwoFactor();
loadToken();
loadTwoFactor();
</script>
<style>
.btn {
background-color: var(--primary);
margin: auto 0;
margin-left: 1rem;
font-size: 1rem;
padding: 0 0.5rem;
}
.floating {
margin-bottom: 0;
}
.input-container {
display: flex;
}
.input-container > *:first-child {
flex-grow: 1;
}
select {
background-color: unset;
border: 0;
border-radius: 0;
color: unset;
font-size: unset;
border-bottom: 1px solid #757575;
/* Firefox */
-moz-appearance: none;
/* Safari and Chrome */
-webkit-appearance: none;
appearance: none;
height: 100%;
width: 100%;
}
select > option {
background-color: unset;
}
.select-wrapper {
position: relative;
}
.select-wrapper::after {
content: ">";
display: block;
position: absolute;
right: 2rem;
top: 0;
bottom: 0;
width: 1rem;
transform: rotate(90deg) scaleY(2);
}
</style>
<Box>
<h1>Two Factor</h1>
<BoxItem name="Add new" open={false}></BoxItem>
{#each twofactor as t}
<BoxItem name={TFATypes.get(t.type)} value={t.name} highlight={t.isthis}>
<button class="btn" style="background: var(--error)" on:click={()=>deleteTFA(t.id)}>Delete</button>
</BoxItem>
{/each}
<!-- <BoxItem name="Name" value={name} open={false}>
<h1>Two Factor</h1>
<BoxItem name="Add new" open={false} />
{#each twofactor as t}
<BoxItem name={TFATypes.get(t.type)} value={t.name} highlight={t.isthis}>
<button
class="btn"
style="background: var(--error)"
on:click={() => deleteTFA(t.id)}>
Delete
</button>
</BoxItem>
{/each}
<!-- <BoxItem name="Name" value={name} open={false}>
<div class="input-container">
<div class="floating group">
<input type="text" autocomplete="username" bind:value={name}>
@ -82,74 +166,21 @@
</Box>
<Box>
<h1>Anmeldungen</h1>
<h1>Anmeldungen</h1>
{#each token as t}
<BoxItem name={t.browser} value={t.ip} highlight={t.isthis}>
<button class="btn" style="background: var(--error)" on:click={()=>revoke(t.id)}>Revoke</button>
</BoxItem>
{:else}
<span>No Tokens</span>
{/each}
{#each token as t}
<BoxItem name={t.browser} value={t.ip} highlight={t.isthis}>
<button
class="btn"
style="background: var(--error)"
on:click={() => revoke(t.id)}>
Revoke
</button>
</BoxItem>
{:else}
<span>No Tokens</span>
{/each}
<!-- <BoxItem name="E-Mail" value={email} />
<!-- <BoxItem name="E-Mail" value={email} />
<BoxItem name="Phone" value={phone} /> -->
</Box>
<style>
.btn {
background-color: var(--primary);
margin: auto 0;
margin-left: 1rem;
font-size: 1rem;
padding: 0 0.5rem;
}
.floating {
margin-bottom: 0;
}
.input-container {
display: flex;
}
.input-container>*:first-child {
flex-grow: 1;
}
select {
background-color: unset;
border: 0;
border-radius: 0;
color: unset;
font-size: unset;
border-bottom: 1px solid #757575;
/* Firefox */
-moz-appearance: none;
/* Safari and Chrome */
-webkit-appearance: none;
appearance: none;
height: 100%;
width: 100%;
}
select>option {
background-color: unset;
}
.select-wrapper {
position: relative;
}
.select-wrapper::after {
content: ">";
display: block;
position: absolute;
right: 2rem;
top: 0;
bottom: 0;
width: 1rem;
transform: rotate(90deg) scaleY(2);
}
</style>