New login form

This commit is contained in:
silverpill 2022-08-13 22:56:16 +00:00
parent e88635b09e
commit 4902f089e6
2 changed files with 191 additions and 103 deletions

View file

@ -62,10 +62,11 @@ export class ProfileWrapper {
} }
export interface UserCreateForm { interface UserCreateForm {
username: string; username: string;
message: string; password: string | null;
signature: string; message: string | null;
signature: string | null;
invite_code: string | null; invite_code: string | null;
} }
@ -83,15 +84,21 @@ export async function createUser(userData: UserCreateForm): Promise<User> {
} }
} }
interface LoginForm {
username: string | null;
password: string | null;
message: string | null;
signature: string | null;
}
export async function getAccessToken( export async function getAccessToken(
message: string, loginType: "password" | "eip4361",
signature: string, loginData: LoginForm,
): Promise<string> { ): Promise<string> {
const url = `${BACKEND_URL}/oauth/token` const url = `${BACKEND_URL}/oauth/token`
const tokenRequestData = { const tokenRequestData = {
grant_type: "eip4361", grant_type: loginType,
message: message, ...loginData,
signature: signature,
} }
const response = await http(url, { const response = await http(url, {
method: "POST", method: "POST",

View file

@ -8,17 +8,26 @@
<br> <br>
<router-link :to="{name: 'about-public'}">Learn more <span class="arrow">&gt;&gt;</span></router-link> <router-link :to="{name: 'about-public'}">Learn more <span class="arrow">&gt;&gt;</span></router-link>
</div> </div>
<div class="login">
<button @click="login()">Sign In</button>
<div v-if="loginErrorMessage" class="error-message">{{ loginErrorMessage }}</div>
</div>
</div> </div>
<form v-if="instance" class="registration-form"> <form v-if="instance" class="login-form">
<div v-if="isLoading" class="registration-form-loader"> <div v-if="isLoading" class="login-form-loader">
<loader></loader> <loader></loader>
</div> </div>
<div class="form-title">Want to join?</div> <div class="login-type">
<div class="form-control"> <button
:class="{ active: loginType === 'password' }"
@click.prevent="loginType = 'password'; loginErrorMessage = null"
>
Password
</button>
<button
:class="{ active: loginType === 'eip4361' }"
@click.prevent="loginType = 'eip4361'; loginErrorMessage = null"
>
Ethereum
</button>
</div>
<div class="form-control" v-if="!isRegistered || loginType == 'password'">
<div class="input-group"> <div class="input-group">
<input <input
id="username" id="username"
@ -30,7 +39,16 @@
</div> </div>
<div class="form-message">Only letters, numbers and underscores are allowed.</div> <div class="form-message">Only letters, numbers and underscores are allowed.</div>
</div> </div>
<div class="form-control" v-if="!instance.registrations"> <div class="form-control" v-if="loginType === 'password'">
<input
id="password"
type="password"
v-model="password"
required
placeholder="Password"
>
</div>
<div class="form-control" v-if="!instance.registrations && !isRegistered">
<input <input
id="invite-token" id="invite-token"
v-model="inviteCode" v-model="inviteCode"
@ -38,16 +56,36 @@
placeholder="Enter the invite code" placeholder="Enter the invite code"
> >
</div> </div>
<div class="wallet-required"> <div class="wallet-required" v-if="loginType === 'eip4361'">
<img :src="require('@/assets/forkawesome/ethereum.svg')"> <img :src="require('@/assets/forkawesome/ethereum.svg')">
<router-link :to="{ name: 'ethereum' }">Ethereum Wallet</router-link> is required <router-link :to="{ name: 'ethereum' }">Ethereum Wallet</router-link> is required
</div> </div>
<button <button
v-if="isRegistered"
type="submit" type="submit"
:disabled="!username" :disabled="!isLoginFormValid()"
@click.prevent="login()"
>
Sign in
</button>
<button
v-else
type="submit"
:disabled="!isLoginFormValid()"
@click.prevent="register()" @click.prevent="register()"
>Sign Up</button> >
<div v-if="registrationErrorMessage" class="error-message">{{ registrationErrorMessage }}</div> Sign Up
</button>
<div class="error-message" v-if="loginErrorMessage" >{{ loginErrorMessage }}</div>
<div class="switch-mode">
<template v-if="isRegistered">Don't have an account?</template>
<template v-else>Already registered?</template>
&thinsp;
<button @click.prevent="isRegistered = !isRegistered; loginErrorMessage = null">
<template v-if="isRegistered">Sign Up</template>
<template v-else>Sign In</template>
</button>
</div>
</form> </form>
</div> </div>
</div> </div>
@ -71,42 +109,78 @@ const router = useRouter()
const { setCurrentUser, setAuthToken } = useCurrentUser() const { setCurrentUser, setAuthToken } = useCurrentUser()
const { instance } = $(useInstanceInfo()) const { instance } = $(useInstanceInfo())
const isRegistered = $ref(true)
const loginType = $ref<"password" | "eip4361">("password")
const username = $ref("") const username = $ref("")
const password = $ref<string | null>(null)
const inviteCode = $ref<string | null>(null) const inviteCode = $ref<string | null>(null)
let isLoading = $ref(false) let isLoading = $ref(false)
let loginErrorMessage = $ref<string | null>(null) let loginErrorMessage = $ref<string | null>(null)
let registrationErrorMessage = $ref<string | null>(null)
function isLoginFormValid(): boolean {
if (!instance) {
return false
}
if (isRegistered) {
if (loginType === "password") {
return Boolean(username) && Boolean(password)
} else {
return true
}
} else {
const inviteCodeValid = instance.registrations ? true : Boolean(inviteCode)
if (loginType === "password") {
return Boolean(username) && Boolean(password) && inviteCodeValid
} else {
return Boolean(username) && inviteCodeValid
}
}
}
async function register() { async function register() {
registrationErrorMessage = null loginErrorMessage = null
if (!instance) { if (!instance) {
return return
} }
const instanceHost = instance.uri let userData
const loginMessage = instance.login_message let loginData
const signer = await getWallet() if (loginType === "password") {
if (!signer) { userData = {
return username,
password,
message: null,
signature: null,
invite_code: inviteCode,
}
loginData = { username, password, message: null, signature: null }
} else {
const signer = await getWallet()
if (!signer) {
return
}
const { message, signature } = await createEip4361_SignedMessage(
signer,
instance.uri,
instance.login_message,
)
userData = {
username,
password: null,
message,
signature,
invite_code: inviteCode,
}
loginData = { username: null, password: null, message, signature }
} }
const { message, signature } = await createEip4361_SignedMessage(
signer,
instanceHost,
loginMessage,
)
isLoading = true isLoading = true
let user let user
let authToken let authToken
try { try {
user = await createUser({ user = await createUser(userData)
username: username, authToken = await getAccessToken(loginType, loginData)
message,
signature,
invite_code: inviteCode,
})
authToken = await getAccessToken(message, signature)
} catch (error: any) { } catch (error: any) {
isLoading = false isLoading = false
registrationErrorMessage = error.message loginErrorMessage = error.message
return return
} }
setCurrentUser(user) setCurrentUser(user)
@ -120,28 +194,35 @@ async function login() {
if (!instance) { if (!instance) {
return return
} }
const instanceHost = instance.uri let loginData
const loginMessage = instance.login_message if (loginType === "password") {
const signer = await getWallet() loginData = { username, password, message: null, signature: null }
if (!signer) { } else {
return const signer = await getWallet()
if (!signer) {
return
}
const { message, signature } = await createEip4361_SignedMessage(
signer,
instance.uri,
instance.login_message,
)
loginData = { username: null, password: null, message, signature }
} }
const { message, signature } = await createEip4361_SignedMessage( isLoading = true
signer,
instanceHost,
loginMessage,
)
let user let user
let authToken let authToken
try { try {
authToken = await getAccessToken(message, signature) authToken = await getAccessToken(loginType, loginData)
user = await getCurrentUser(authToken) user = await getCurrentUser(authToken)
} catch (error: any) { } catch (error: any) {
isLoading = false
loginErrorMessage = error.message loginErrorMessage = error.message
return return
} }
setCurrentUser(user) setCurrentUser(user)
setAuthToken(authToken) setAuthToken(authToken)
isLoading = false
router.push({ name: "home" }) router.push({ name: "home" })
} }
</script> </script>
@ -152,18 +233,6 @@ async function login() {
$text-color: #fff; $text-color: #fff;
button {
background-color: #000;
border: none;
border-radius: 10px;
color: $text-color;
cursor: pointer;
display: block;
font-size: 20px;
font-weight: bold;
padding: 10px 60px;
}
.landing-page { .landing-page {
background-color: #000; background-color: #000;
background-image: url("../assets/startpage.png"); background-image: url("../assets/startpage.png");
@ -215,50 +284,21 @@ button {
} }
} }
.login { .login-form {
display: inline-block; background-color: #1A1818;
margin-top: 30px;
text-align: center;
button {
border: 1px solid #979797;
box-shadow: 0 2px 16px -5px #6E6E6E;
&:hover {
background-color: #fff;
border-color: #fff;
color: #000;
}
}
.error-message {
color: $error-color;
margin-top: 5px;
}
}
.registration-form {
border: 1px solid #979797;
border-radius: 10px; border-radius: 10px;
box-sizing: border-box; box-sizing: border-box;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 15px; gap: 15px;
min-width: $wide-sidebar-width - 50px; min-width: $wide-sidebar-width - 50px;
padding: 25px 40px; padding: 30px;
position: relative; position: relative;
width: $wide-sidebar-width; width: $wide-sidebar-width;
.form-title {
font-size: 24px;
font-weight: bold;
margin-bottom: 10px;
text-align: center;
}
input, input,
.addon { .addon {
background-color: #201f1f; background-color: #2E2C2C;
border: none; border: none;
line-height: 18px; line-height: 18px;
padding: 15px; padding: 15px;
@ -297,13 +337,21 @@ button {
padding: 0 15px; padding: 0 15px;
} }
button { button[type="submit"] {
background: linear-gradient(to right, #FF5959, #FF5EAD, #D835FE, #D963FF); background: linear-gradient(to right, #FF5959, #FF5EAD, #D835FE, #D963FF);
background-color: #000;
border: none;
border-radius: 10px;
box-shadow: 0 2px 16px -5px #BB5CC7; box-shadow: 0 2px 16px -5px #BB5CC7;
color: $text-color;
cursor: pointer;
display: block;
font-size: 20px;
font-weight: bold;
height: 48px; height: 48px;
margin-top: 5px; padding: 10px 60px;
&:hover { &:not([disabled]):hover {
background: linear-gradient(to right, #FF7373, #FF78BA, #DD4FFE, #DF7DFF); background: linear-gradient(to right, #FF7373, #FF78BA, #DD4FFE, #DF7DFF);
} }
} }
@ -331,9 +379,18 @@ button {
text-decoration: underline; text-decoration: underline;
} }
} }
.switch-mode {
text-align: center;
button {
color: $text-color;
text-decoration: underline;
}
}
} }
.registration-form-loader { .login-form-loader {
bottom: 0; bottom: 0;
display: flex; display: flex;
left: 0; left: 0;
@ -352,8 +409,32 @@ button {
margin: 0 auto; margin: 0 auto;
} }
.login-type {
border-radius: 10px;
display: flex;
button {
border: 1px solid #3D3D3D;
color: #fff;
padding: 10px;
width: 100%;
&:first-child {
border-radius: 10px 0 0 10px;
}
&:last-child {
border-radius: 0 10px 10px 0;
}
&.active {
background-color: #3D3D3D;
}
}
}
@media screen and (max-width: $screen-breakpoint-medium) { @media screen and (max-width: $screen-breakpoint-medium) {
.registration-form { .login-form {
padding: 25px; padding: 25px;
} }
} }
@ -368,7 +449,7 @@ button {
justify-content: flex-start; justify-content: flex-start;
} }
.registration-form { .login-form {
margin-right: auto; margin-right: auto;
min-width: auto; min-width: auto;