Require email account authentication
All checks were successful
CI / validate (push) Successful in 14m23s
All checks were successful
CI / validate (push) Successful in 14m23s
This commit is contained in:
@@ -19,6 +19,13 @@ interface AuthResponse {
|
||||
user: DngAuthUser
|
||||
}
|
||||
|
||||
interface SignUpResponse {
|
||||
access_token?: string | null
|
||||
refresh_token?: string | null
|
||||
expires_in?: number | null
|
||||
user: DngAuthUser
|
||||
}
|
||||
|
||||
const STORAGE_KEY = 'dng-auth-session'
|
||||
|
||||
export function useDngAuth() {
|
||||
@@ -48,6 +55,10 @@ export function useDngAuth() {
|
||||
}
|
||||
}
|
||||
|
||||
function isEmailAccount(user: DngAuthUser | undefined): boolean {
|
||||
return Boolean(user?.email && !user.is_anonymous)
|
||||
}
|
||||
|
||||
function parseStoredSession(value: string): DngSession | null {
|
||||
try {
|
||||
const saved = JSON.parse(value) as Partial<DngSession>
|
||||
@@ -97,7 +108,11 @@ export function useDngAuth() {
|
||||
}
|
||||
|
||||
async function restore() {
|
||||
if (!import.meta.client || hydrated.value) return session.value
|
||||
if (!import.meta.client) return session.value
|
||||
if (hydrated.value) {
|
||||
if (session.value && !isEmailAccount(session.value.user)) persist(null)
|
||||
return session.value
|
||||
}
|
||||
|
||||
let restoredFromStorage = false
|
||||
const hash = new URLSearchParams(window.location.hash.replace(/^#/, ''))
|
||||
@@ -133,37 +148,76 @@ export function useDngAuth() {
|
||||
await refresh()
|
||||
}
|
||||
}
|
||||
if (session.value && !isEmailAccount(session.value.user)) persist(null)
|
||||
hydrated.value = true
|
||||
return session.value
|
||||
}
|
||||
|
||||
async function requestMagicLink(email: string) {
|
||||
async function signUp(displayName: string, email: string, password: string) {
|
||||
const redirectTo = `${window.location.origin}/auth/callback`
|
||||
await $fetch(`${config.public.supabaseUrl}/auth/v1/otp?redirect_to=${encodeURIComponent(redirectTo)}`, {
|
||||
method: 'POST',
|
||||
headers: authHeaders(),
|
||||
body: { email: email.trim().toLowerCase(), create_user: true },
|
||||
})
|
||||
}
|
||||
|
||||
async function signInAnonymously() {
|
||||
const response = await $fetch<AuthResponse>(`${config.public.supabaseUrl}/auth/v1/signup`, {
|
||||
const response = await $fetch<SignUpResponse>(`${config.public.supabaseUrl}/auth/v1/signup?redirect_to=${encodeURIComponent(redirectTo)}`, {
|
||||
method: 'POST',
|
||||
headers: authHeaders(),
|
||||
body: {
|
||||
data: { display_name: 'Guest Adventurer' },
|
||||
gotrue_meta_security: {},
|
||||
email: email.trim().toLowerCase(),
|
||||
password,
|
||||
data: { display_name: displayName.trim() },
|
||||
},
|
||||
})
|
||||
if (response.access_token && response.refresh_token && response.expires_in) {
|
||||
const next = fromResponse({
|
||||
access_token: response.access_token,
|
||||
refresh_token: response.refresh_token,
|
||||
expires_in: response.expires_in,
|
||||
user: response.user,
|
||||
})
|
||||
persist(next)
|
||||
hydrated.value = true
|
||||
return { session: next, needsEmailConfirmation: false }
|
||||
}
|
||||
return { session: null, needsEmailConfirmation: true }
|
||||
}
|
||||
|
||||
async function signIn(email: string, password: string) {
|
||||
const response = await $fetch<AuthResponse>(`${config.public.supabaseUrl}/auth/v1/token?grant_type=password`, {
|
||||
method: 'POST',
|
||||
headers: authHeaders(),
|
||||
body: { email: email.trim().toLowerCase(), password },
|
||||
})
|
||||
const next = fromResponse(response)
|
||||
if (!isEmailAccount(next.user)) throw new Error('An email account is required.')
|
||||
persist(next)
|
||||
hydrated.value = true
|
||||
return next
|
||||
}
|
||||
|
||||
async function requestPasswordReset(email: string) {
|
||||
const redirectTo = `${window.location.origin}/auth/reset-password`
|
||||
await $fetch(`${config.public.supabaseUrl}/auth/v1/recover?redirect_to=${encodeURIComponent(redirectTo)}`, {
|
||||
method: 'POST',
|
||||
headers: authHeaders(),
|
||||
body: { email: email.trim().toLowerCase() },
|
||||
})
|
||||
}
|
||||
|
||||
async function updatePassword(password: string) {
|
||||
const token = await accessToken()
|
||||
const user = normalizeUser(await $fetch<DngAuthUser>(`${config.public.supabaseUrl}/auth/v1/user`, {
|
||||
method: 'PUT',
|
||||
headers: authHeaders(token),
|
||||
body: { password },
|
||||
}))
|
||||
if (session.value) persist({ ...session.value, user })
|
||||
return user
|
||||
}
|
||||
|
||||
async function accessToken() {
|
||||
await restore()
|
||||
if (!session.value) throw new Error('Enter the alpha to continue.')
|
||||
if (!isEmailAccount(session.value.user)) {
|
||||
persist(null)
|
||||
throw new Error('Sign in with an email account to continue.')
|
||||
}
|
||||
if (session.value.expiresAt <= Date.now() + 60_000) await refresh()
|
||||
if (!session.value) throw new Error('Your session expired. Sign in again.')
|
||||
return session.value.accessToken
|
||||
@@ -184,5 +238,5 @@ export function useDngAuth() {
|
||||
hydrated.value = true
|
||||
}
|
||||
|
||||
return { session, hydrated, restore, refresh, requestMagicLink, signInAnonymously, accessToken, signOut, invalidate }
|
||||
return { session, hydrated, restore, refresh, signUp, signIn, requestPasswordReset, updatePassword, accessToken, signOut, invalidate }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user