toolshed/frontend/src/views/Pairing.vue
2026-08-27 19:05:44 +02:00

236 lines
No EOL
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="main d-flex w-100">
<div class="container d-flex flex-column">
<div class="row vh-100">
<div class="col-sm-10 col-md-8 col-lg-6 mx-auto d-table h-100">
<div class="d-table-cell align-middle">
<div class="text-center mt-4">
<h1 class="h2">
Toolshed
</h1>
<p class="lead" v-if="msg">
{{ msg }}
</p>
<p class="lead" v-else>
Create an account to get started
</p>
</div>
<div class="card">
<div class="card-body">
<div class="m-sm-4">
<div :class="errors.raw_record?['mb-3','is-invalid']:['mb-3']">
<label class="form-label">Identity Record</label>
<div class="input-group">
<input class="form-control form-control-lg" type="text"
:class="errors.raw_record?['is-invalid']:[]"
v-model="raw_record" placeholder="Paste identity record"
@input.prevent="try_parsing">
<span class="input-group-text form-control-lg" v-if="raw_record == ''"
@click="pasteClipboard()">
<b-icon-clipboard></b-icon-clipboard>
</span>
<span class="input-group-text form-control-lg" v-else
@click="raw_record = ''">
<b-icon-x-circle></b-icon-x-circle>
</span>
</div>
<div class="invalid-feedback">
{{ errors.raw_record }}
</div>
</div>
<div class="mb-3" v-if="is_unlocked || is_locked">
<label class="form-label">Username</label>
<div class="input-group">
<input class="form-control form-control-lg" type="email"
disabled v-model="username">
<span class="input-group-text form-control-lg bg-success"
v-if="is_unlocked">
<b-icon-unlock></b-icon-unlock>
</span>
<span class="input-group-text form-control-lg bg-warning"
v-if="is_locked">
<b-icon-lock></b-icon-lock>
</span>
</div>
</div>
<div class="mb-3" v-if="is_locked" :class="errors.password?['is-invalid']:[]">
<form role="form" method="post" @submit.prevent="try_unlock">
<label class="form-label">Password</label>
<div class="input-group">
<input class="form-control form-control-lg" type="password"
name="password" placeholder="Enter your password"
v-model="password" :class="errors.password?['is-invalid']:[]">
<button class="btn btn-lg btn-primary" type="button"
@click="try_unlock">
<b-icon-unlock></b-icon-unlock>
unlock
</button>
</div>
<div class="invalid-feedback">
{{ errors.password }}
</div>
</form>
</div>
<div>
<label class="form-check">
<input class="form-check-input" type="checkbox" value="remember-me"
name="remember-me" checked v-model="remember"
@change="setRemember(remember)">
<span class="form-check-label">
Remember me next time
</span>
</label>
</div>
<div class="text-center mt-3">
<button type="submit" class="btn btn-lg btn-primary" :disabled="!is_unlocked"
@click="try_pairing">
Pair
</button>
</div>
<br/>
<div class="text-center">
<p class="mb-0 text-muted">
Already have an account?
<router-link to="/login">Login</router-link>
</p>
<p class="mb-0 text-muted">
Dont have an account?
<router-link to="/register">Sign up</router-link>
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import {mapActions, mapMutations} from 'vuex';
import {ServerSet, createSignAuth} from '../federation';
import NeighborsCache from '../neigbors';
import {decryptIdentityRecord, parseIdentityRecord, serializeIdentityRecord} from "@/identity";
import router from "@/router";
export default {
name: 'Pairing',
data() {
return {
msg: 'Pair with existing Account',
//raw_record: 'test_a@a.localhost:4ab79601edc400fd0263c7d5fd045ea7f5de28f1dd8905e2479f96893f3257d8:7f0ee42b',
//raw_record: 'test_a@a.localhost:$pbkdf2$100000$67da1c2a055dd41fc9707c37d2cdecea$8173b4d626352bcafab626b2$60027066142f55ecfd6f91b587f99c5f02ee536ce08c31e3f69148a51711489220a2b10d5450166ee09a781f891be8ab3a418e31a8e71f6468f189ba0baf31ece21166b63ba608683bdbea8afa7dfc33b1a994d8be4e4b2123',
raw_record: '',
crypted_record: null,
username: '',
password: '',
keypair: '',
remember: false,
errors: {}
}
},
computed: {
is_unlocked() {
return !!this.keypair && !!this.username;
},
is_locked() {
return !!this.username && !!this.crypted_record && !this.keypair;
}
},
methods: {
...mapActions(['lookupServer']),
...mapMutations(['setRemember', 'setUserFromIdentityRecord']),
async try_pairing() {
const unreachable = new NeighborsCache();
const servers = await this.lookupServer({username: this.username}).then(servers => new ServerSet(servers, unreachable))
const auth = createSignAuth(this.username, this.keypair.signSk);
const reply = await servers.getRaw(auth, '/api/v1/friends/');
if (reply.status === 200) {
this.setUserFromIdentityRecord({username: this.username, keypair: this.keypair});
router.push({path: '/'});
} else {
console.error('Error:', reply);
}
},
try_parsing() {
if (!this.raw_record) {
this.username = '';
this.keypair = '';
this.errors = {};
return;
}
const parsed = parseIdentityRecord(this.raw_record)
const {user, keypair, errors} = parsed;
if (user) {
this.username = user;
}
if (errors) {
this.errors.raw_record = errors;
}
if (keypair) {
this.keypair = keypair;
}
if (parsed.crypted) {
this.crypted_record = parsed;
}
},
async try_unlock() {
const {
user,
keypair,
errors
} = await decryptIdentityRecord(this.crypted_record, this.username, this.password);
if (user) {
this.username = user;
}
if (errors) {
this.errors.password = errors;
}
if (keypair) {
this.keypair = keypair;
}
},
async pasteClipboard() {
try {
this.raw_record = await navigator.clipboard.readText();
this.try_parsing()
} catch (e) {
console.error('Error:', e);
}
}
},
mounted() {
try {
fetch('/local/domains')
.then(response => response.json())
.then(data => {
this.domains = data;
});
} catch (e) {
console.error('Error:', e);
}
}
}
</script>
<style scoped>
.is-invalid input, .is-invalid select {
border: 1px solid var(--bs-danger);
}
.is-invalid .invalid-feedback {
display: block;
}
.input-group-text svg {
overflow: visible;
}
</style>