Initial
This commit is contained in:
commit
b43955e0d9
162 changed files with 15425 additions and 0 deletions
22
src/lib/auth/guard.ts
Normal file
22
src/lib/auth/guard.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import { goto } from '$app/navigation';
|
||||
import { authStore } from './store';
|
||||
|
||||
/** Call in a page's onMount. Redirects anonymous users to `redirectTo`. */
|
||||
export async function requireAuthenticated(redirectTo = '/login'): Promise<boolean> {
|
||||
const state = await authStore.refresh();
|
||||
if (state.status !== 'authenticated') {
|
||||
await goto(redirectTo);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Like requireAuthenticated but also requires staff. */
|
||||
export async function requireStaff(redirectTo = '/app'): Promise<boolean> {
|
||||
const state = await authStore.refresh();
|
||||
if (state.status !== 'authenticated' || !state.isStaff) {
|
||||
await goto(redirectTo);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
2
src/lib/auth/index.ts
Normal file
2
src/lib/auth/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
export * from './store';
|
||||
export * from './guard';
|
||||
70
src/lib/auth/store.ts
Normal file
70
src/lib/auth/store.ts
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
import { writable, get } from 'svelte/store';
|
||||
import { goto } from '$app/navigation';
|
||||
import { setUnauthorizedHandler, authApi } from '$api';
|
||||
|
||||
export type AuthStatus = 'unknown' | 'anonymous' | 'authenticated';
|
||||
export interface AuthState {
|
||||
status: AuthStatus;
|
||||
username: string | null;
|
||||
isStaff: boolean;
|
||||
}
|
||||
|
||||
const initial: AuthState = { status: 'unknown', username: null, isStaff: false };
|
||||
|
||||
function createAuthStore() {
|
||||
const { subscribe, set } = writable<AuthState>(initial);
|
||||
|
||||
async function refresh(): Promise<AuthState> {
|
||||
try {
|
||||
const session = await authApi.session();
|
||||
if (!session.isAuthenticated) {
|
||||
const next: AuthState = { status: 'anonymous', username: null, isStaff: false };
|
||||
set(next);
|
||||
return next;
|
||||
}
|
||||
const me = await authApi.whoami();
|
||||
const next: AuthState = {
|
||||
status: 'authenticated',
|
||||
username: me.username,
|
||||
isStaff: me.is_staff
|
||||
};
|
||||
set(next);
|
||||
return next;
|
||||
} catch {
|
||||
const next: AuthState = { status: 'anonymous', username: null, isStaff: false };
|
||||
set(next);
|
||||
return next;
|
||||
}
|
||||
}
|
||||
|
||||
async function login(username: string, password: string) {
|
||||
await authApi.login(username, password);
|
||||
await refresh();
|
||||
}
|
||||
|
||||
async function register(username: string, email: string, password: string) {
|
||||
await authApi.register(username, email, password);
|
||||
await refresh();
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
try {
|
||||
await authApi.logout();
|
||||
} finally {
|
||||
set({ status: 'anonymous', username: null, isStaff: false });
|
||||
}
|
||||
}
|
||||
|
||||
return { subscribe, refresh, login, register, logout, snapshot: () => get({ subscribe }) };
|
||||
}
|
||||
|
||||
export const authStore = createAuthStore();
|
||||
|
||||
// Route API 401s back through the store so an expired session redirects once.
|
||||
let redirecting = false;
|
||||
setUnauthorizedHandler(() => {
|
||||
if (redirecting || typeof window === 'undefined') return;
|
||||
if (window.location.pathname.startsWith('/login')) return;
|
||||
redirecting = true;
|
||||
goto('/login').finally(() => (redirecting = false));
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue