mirror of
https://github.com/h44z/wg-portal.git
synced 2025-06-27 16:57:01 +00:00
309 lines
13 KiB
JavaScript
309 lines
13 KiB
JavaScript
import { defineStore } from 'pinia'
|
|
|
|
import { notify } from "@kyvg/vue3-notification";
|
|
import { apiWrapper } from '@/helpers/fetch-wrapper'
|
|
import router from '../router'
|
|
import { browserSupportsWebAuthn,startRegistration,startAuthentication } from '@simplewebauthn/browser';
|
|
import {base64_url_encode} from "@/helpers/encoding";
|
|
|
|
export const authStore = defineStore('auth',{
|
|
state: () => ({
|
|
// initialize state from local storage to enable user to stay logged in
|
|
user: JSON.parse(localStorage.getItem('user')),
|
|
providers: [],
|
|
returnUrl: localStorage.getItem('returnUrl'),
|
|
webAuthnCredentials: [],
|
|
fetching: false,
|
|
}),
|
|
getters: {
|
|
UserIdentifier: (state) => state.user?.Identifier || 'unknown',
|
|
User: (state) => state.user,
|
|
LoginProviders: (state) => state.providers,
|
|
IsAuthenticated: (state) => state.user != null,
|
|
IsAdmin: (state) => state.user?.IsAdmin || false,
|
|
ReturnUrl: (state) => state.returnUrl || '/',
|
|
IsWebAuthnEnabled: (state) => {
|
|
if (state.webAuthnCredentials) {
|
|
return state.webAuthnCredentials.length > 0
|
|
}
|
|
return false
|
|
},
|
|
WebAuthnCredentials: (state) => state.webAuthnCredentials || [],
|
|
isFetching: (state) => state.fetching,
|
|
},
|
|
actions: {
|
|
SetReturnUrl(link) {
|
|
this.returnUrl = link
|
|
localStorage.setItem('returnUrl', link)
|
|
},
|
|
ResetReturnUrl() {
|
|
this.returnUrl = null
|
|
localStorage.removeItem('returnUrl')
|
|
},
|
|
// LoadProviders always returns a fulfilled promise, even if the request failed.
|
|
async LoadProviders() {
|
|
apiWrapper.get(`/auth/providers`)
|
|
.then(providers => this.providers = providers)
|
|
.catch(error => {
|
|
this.providers = []
|
|
console.log("Failed to load auth providers: ", error)
|
|
notify({
|
|
title: "Backend Connection Failure",
|
|
text: "Failed to load external authentication providers!",
|
|
})
|
|
})
|
|
},
|
|
|
|
// LoadSession returns promise that might have been rejected if the session was not authenticated.
|
|
async LoadSession() {
|
|
return apiWrapper.get(`/auth/session`)
|
|
.then(session => {
|
|
if (session.LoggedIn === true) {
|
|
this.ResetReturnUrl()
|
|
this.setUserInfo(session)
|
|
return session.UserIdentifier
|
|
} else {
|
|
this.setUserInfo(null)
|
|
return Promise.reject(new Error('session not authenticated'))
|
|
}
|
|
})
|
|
.catch(err => {
|
|
this.setUserInfo(null)
|
|
return Promise.reject(err)
|
|
})
|
|
},
|
|
// LoadWebAuthnCredentials returns promise that might have been rejected if the session was not authenticated.
|
|
async LoadWebAuthnCredentials() {
|
|
this.fetching = true
|
|
return apiWrapper.get(`/auth/webauthn/credentials`)
|
|
.then(credentials => {
|
|
this.setWebAuthnCredentials(credentials)
|
|
})
|
|
.catch(error => {
|
|
this.setWebAuthnCredentials([])
|
|
console.log("Failed to load webauthn credentials:", error)
|
|
notify({
|
|
title: "Backend Connection Failure",
|
|
text: error,
|
|
type: 'error',
|
|
})
|
|
})
|
|
},
|
|
// Login returns promise that might have been rejected if the login attempt was not successful.
|
|
async Login(username, password) {
|
|
return apiWrapper.post(`/auth/login`, { username, password })
|
|
.then(user => {
|
|
this.ResetReturnUrl()
|
|
this.setUserInfo(user)
|
|
return user.Identifier
|
|
})
|
|
.catch(err => {
|
|
console.log("Login failed:", err)
|
|
this.setUserInfo(null)
|
|
return Promise.reject(new Error("login failed"))
|
|
})
|
|
},
|
|
async Logout() {
|
|
this.setUserInfo(null)
|
|
this.ResetReturnUrl() // just to be sure^^
|
|
|
|
try {
|
|
await apiWrapper.post(`/auth/logout`)
|
|
} catch (e) {
|
|
console.log("Logout request failed:", e)
|
|
}
|
|
|
|
notify({
|
|
title: "Logged Out",
|
|
text: "Logout successful!",
|
|
type: "warn",
|
|
})
|
|
|
|
|
|
await router.push('/login')
|
|
},
|
|
async RegisterWebAuthn() {
|
|
// check if the browser supports WebAuthn
|
|
if (!browserSupportsWebAuthn()) {
|
|
console.error("WebAuthn is not supported by this browser.");
|
|
notify({
|
|
title: "WebAuthn not supported",
|
|
text: "This browser does not support WebAuthn.",
|
|
type: 'error'
|
|
});
|
|
return Promise.reject(new Error("WebAuthn not supported"));
|
|
}
|
|
|
|
this.fetching = true
|
|
console.log("Starting WebAuthn registration...")
|
|
await apiWrapper.post(`/auth/webauthn/register/start`, {})
|
|
.then(optionsJSON => {
|
|
notify({
|
|
title: "Passkey registration",
|
|
text: "Starting passkey registration, follow the instructions in the browser."
|
|
});
|
|
console.log("Started WebAuthn registration with options: ", optionsJSON)
|
|
|
|
return startRegistration({ optionsJSON: optionsJSON.publicKey }).then(attResp => {
|
|
console.log("Finishing WebAuthn registration...")
|
|
return apiWrapper.post(`/auth/webauthn/register/finish`, attResp)
|
|
.then(credentials => {
|
|
console.log("Passkey registration finished successfully: ", credentials)
|
|
this.setWebAuthnCredentials(credentials)
|
|
notify({
|
|
title: "Passkey registration",
|
|
text: "A new passkey has been registered successfully!",
|
|
type: 'success'
|
|
});
|
|
})
|
|
.catch(err => {
|
|
this.fetching = false
|
|
console.error("Failed to register passkey:", err);
|
|
notify({
|
|
title: "Passkey registration failed",
|
|
text: err,
|
|
type: 'error'
|
|
});
|
|
})
|
|
}).catch(err => {
|
|
this.fetching = false
|
|
console.error("Failed to start WebAuthn registration:", err);
|
|
notify({
|
|
title: "Failed to start Passkey registration",
|
|
text: err,
|
|
type: 'error'
|
|
});
|
|
})
|
|
})
|
|
.catch(err => {
|
|
this.fetching = false
|
|
console.error("Failed to start WebAuthn registration:", err);
|
|
notify({
|
|
title: "Failed to start WebAuthn registration",
|
|
text: err,
|
|
type: 'error'
|
|
});
|
|
})
|
|
},
|
|
async DeleteWebAuthnCredential(credentialId) {
|
|
this.fetching = true
|
|
return apiWrapper.delete(`/auth/webauthn/credential/${base64_url_encode(credentialId)}`)
|
|
.then(credentials => {
|
|
this.setWebAuthnCredentials(credentials)
|
|
notify({
|
|
title: "Success",
|
|
text: "Passkey deleted successfully!",
|
|
type: 'success',
|
|
})
|
|
})
|
|
.catch(err => {
|
|
this.fetching = false
|
|
console.error("Failed to delete webauthn credential:", err);
|
|
notify({
|
|
title: "Backend Connection Failure",
|
|
text: err,
|
|
type: 'error',
|
|
})
|
|
})
|
|
},
|
|
async RenameWebAuthnCredential(credential) {
|
|
this.fetching = true
|
|
return apiWrapper.put(`/auth/webauthn/credential/${base64_url_encode(credential.ID)}`, {
|
|
Name: credential.Name,
|
|
})
|
|
.then(credentials => {
|
|
this.setWebAuthnCredentials(credentials)
|
|
notify({
|
|
title: "Success",
|
|
text: "Passkey renamed successfully!",
|
|
type: 'success',
|
|
})
|
|
})
|
|
.catch(err => {
|
|
this.fetching = false
|
|
console.error("Failed to rename webauthn credential", credential.ID, ":", err);
|
|
notify({
|
|
title: "Backend Connection Failure",
|
|
text: err,
|
|
type: 'error',
|
|
})
|
|
})
|
|
},
|
|
async LoginWebAuthn() {
|
|
// check if the browser supports WebAuthn
|
|
if (!browserSupportsWebAuthn()) {
|
|
console.error("WebAuthn is not supported by this browser.");
|
|
notify({
|
|
title: "WebAuthn not supported",
|
|
text: "This browser does not support WebAuthn.",
|
|
type: 'error'
|
|
});
|
|
return Promise.reject(new Error("WebAuthn not supported"));
|
|
}
|
|
|
|
this.fetching = true
|
|
console.log("Starting WebAuthn login...")
|
|
await apiWrapper.post(`/auth/webauthn/login/start`, {})
|
|
.then(optionsJSON => {
|
|
console.log("Started WebAuthn login with options: ", optionsJSON)
|
|
|
|
return startAuthentication({ optionsJSON: optionsJSON.publicKey }).then(asseResp => {
|
|
console.log("Finishing WebAuthn login ...")
|
|
return apiWrapper.post(`/auth/webauthn/login/finish`, asseResp)
|
|
.then(user => {
|
|
console.log("Passkey login finished successfully for user:", user.Identifier)
|
|
this.ResetReturnUrl()
|
|
this.setUserInfo(user)
|
|
return user.Identifier
|
|
})
|
|
.catch(err => {
|
|
console.error("Failed to login with passkey:", err)
|
|
this.setUserInfo(null)
|
|
return Promise.reject(new Error("login failed"))
|
|
})
|
|
}).catch(err => {
|
|
console.error("Failed to finish passkey login:", err)
|
|
this.setUserInfo(null)
|
|
return Promise.reject(new Error("login failed"))
|
|
})
|
|
})
|
|
.catch(err => {
|
|
console.error("Failed to start passkey login:", err)
|
|
this.setUserInfo(null)
|
|
return Promise.reject(new Error("login failed"))
|
|
})
|
|
},
|
|
// -- internal setters
|
|
setUserInfo(userInfo) {
|
|
// store user details and jwt in local storage to keep user logged in between page refreshes
|
|
if (userInfo) {
|
|
if ('UserIdentifier' in userInfo) { // session object
|
|
this.user = {
|
|
Identifier: userInfo['UserIdentifier'],
|
|
Firstname: userInfo['UserFirstname'],
|
|
Lastname: userInfo['UserLastname'],
|
|
Email: userInfo['UserEmail'],
|
|
IsAdmin: userInfo['IsAdmin']
|
|
}
|
|
} else { // user object
|
|
this.user = {
|
|
Identifier: userInfo['Identifier'],
|
|
Firstname: userInfo['Firstname'],
|
|
Lastname: userInfo['Lastname'],
|
|
Email: userInfo['Email'],
|
|
IsAdmin: userInfo['IsAdmin']
|
|
}
|
|
}
|
|
localStorage.setItem('user', JSON.stringify(this.user))
|
|
} else {
|
|
this.user = null
|
|
localStorage.removeItem('user')
|
|
}
|
|
},
|
|
setWebAuthnCredentials(credentials) {
|
|
this.fetching = false
|
|
this.webAuthnCredentials = credentials
|
|
}
|
|
}
|
|
});
|