5 changed files with 213 additions and 22 deletions
@ -0,0 +1,71 @@
@@ -0,0 +1,71 @@
|
||||
import { ofetch } from "ofetch"; |
||||
|
||||
export interface SessionResponse { |
||||
id: string; |
||||
userId: string; |
||||
createdAt: string; |
||||
accessedAt: string; |
||||
device: string; |
||||
userAgent: string; |
||||
} |
||||
|
||||
export interface UserResponse { |
||||
id: string; |
||||
namespace: string; |
||||
name: string; |
||||
roles: string[]; |
||||
createdAt: string; |
||||
profile: { |
||||
colorA: string; |
||||
colorB: string; |
||||
icon: string; |
||||
}; |
||||
} |
||||
|
||||
export interface LoginResponse { |
||||
session: SessionResponse; |
||||
token: string; |
||||
} |
||||
|
||||
function getAuthHeaders(token: string): Record<string, string> { |
||||
return { |
||||
authorization: `Bearer ${token}`, |
||||
}; |
||||
} |
||||
|
||||
export async function accountLogin( |
||||
url: string, |
||||
id: string, |
||||
deviceName: string |
||||
): Promise<LoginResponse> { |
||||
return ofetch<LoginResponse>("/auth/login", { |
||||
method: "POST", |
||||
body: { |
||||
id, |
||||
device: deviceName, |
||||
}, |
||||
baseURL: url, |
||||
}); |
||||
} |
||||
|
||||
export async function getUser( |
||||
url: string, |
||||
token: string |
||||
): Promise<UserResponse> { |
||||
return ofetch<UserResponse>("/user/@me", { |
||||
headers: getAuthHeaders(token), |
||||
baseURL: url, |
||||
}); |
||||
} |
||||
|
||||
export async function removeSession( |
||||
url: string, |
||||
token: string, |
||||
sessionId: string |
||||
): Promise<UserResponse> { |
||||
return ofetch<UserResponse>(`/sessions/${sessionId}`, { |
||||
method: "DELETE", |
||||
headers: getAuthHeaders(token), |
||||
baseURL: url, |
||||
}); |
||||
} |
@ -0,0 +1,54 @@
@@ -0,0 +1,54 @@
|
||||
import { useCallback } from "react"; |
||||
|
||||
import { accountLogin, getUser, removeSession } from "@/backend/accounts/auth"; |
||||
import { conf } from "@/setup/config"; |
||||
import { useAuthStore } from "@/stores/auth"; |
||||
|
||||
export function useBackendUrl() { |
||||
const backendUrl = useAuthStore((s) => s.backendUrl); |
||||
return backendUrl ?? conf().BACKEND_URL; |
||||
} |
||||
|
||||
export function useAuth() { |
||||
const currentAccount = useAuthStore((s) => s.account); |
||||
const profile = useAuthStore((s) => s.account?.profile); |
||||
const loggedIn = !!useAuthStore((s) => s.account); |
||||
const setAccount = useAuthStore((s) => s.setAccount); |
||||
const removeAccount = useAuthStore((s) => s.removeAccount); |
||||
const backendUrl = useBackendUrl(); |
||||
|
||||
const login = useCallback( |
||||
async (id: string, device: string) => { |
||||
const account = await accountLogin(backendUrl, id, device); |
||||
const user = await getUser(backendUrl, account.token); |
||||
setAccount({ |
||||
token: account.token, |
||||
userId: user.id, |
||||
sessionId: account.session.id, |
||||
profile: user.profile, |
||||
}); |
||||
}, |
||||
[setAccount, backendUrl] |
||||
); |
||||
|
||||
const logout = useCallback(async () => { |
||||
if (!currentAccount) return; |
||||
try { |
||||
await removeSession( |
||||
backendUrl, |
||||
currentAccount.token, |
||||
currentAccount.sessionId |
||||
); |
||||
} catch { |
||||
// we dont care about failing to delete session
|
||||
} |
||||
removeAccount(); // TODO clear local data
|
||||
}, [removeAccount, backendUrl, currentAccount]); |
||||
|
||||
return { |
||||
loggedIn, |
||||
profile, |
||||
login, |
||||
logout, |
||||
}; |
||||
} |
@ -0,0 +1,58 @@
@@ -0,0 +1,58 @@
|
||||
import { create } from "zustand"; |
||||
import { persist } from "zustand/middleware"; |
||||
import { immer } from "zustand/middleware/immer"; |
||||
|
||||
interface Account { |
||||
profile: { |
||||
colorA: string; |
||||
colorB: string; |
||||
icon: string; |
||||
}; |
||||
} |
||||
|
||||
type AccountWithToken = Account & { |
||||
sessionId: string; |
||||
userId: string; |
||||
token: string; |
||||
}; |
||||
|
||||
interface AuthStore { |
||||
account: null | AccountWithToken; |
||||
backendUrl: null | string; |
||||
proxySet: null | string[]; // TODO actually use these settings
|
||||
removeAccount(): void; |
||||
setAccount(acc: AccountWithToken): void; |
||||
updateAccount(acc: Account): void; |
||||
} |
||||
|
||||
export const useAuthStore = create( |
||||
persist( |
||||
immer<AuthStore>((set) => ({ |
||||
account: null, |
||||
backendUrl: null, |
||||
proxySet: null, |
||||
setAccount(acc) { |
||||
set((s) => { |
||||
s.account = acc; |
||||
}); |
||||
}, |
||||
removeAccount() { |
||||
set((s) => { |
||||
s.account = null; |
||||
}); |
||||
}, |
||||
updateAccount(acc) { |
||||
set((s) => { |
||||
if (!s.account) return; |
||||
s.account = { |
||||
...s.account, |
||||
...acc, |
||||
}; |
||||
}); |
||||
}, |
||||
})), |
||||
{ |
||||
name: "__MW::auth", |
||||
} |
||||
) |
||||
); |
Loading…
Reference in new issue