New login form
This commit is contained in:
parent
e88635b09e
commit
4902f089e6
2 changed files with 191 additions and 103 deletions
|
@ -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",
|
||||||
|
|
|
@ -8,17 +8,26 @@
|
||||||
<br>
|
<br>
|
||||||
<router-link :to="{name: 'about-public'}">Learn more <span class="arrow">>></span></router-link>
|
<router-link :to="{name: 'about-public'}">Learn more <span class="arrow">>></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>
|
||||||
|
 
|
||||||
|
<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;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue