mirror of
https://github.com/movie-web/movie-web.git
synced 2024-12-28 23:26:06 +00:00
account login shit
This commit is contained in:
parent
2953b8f29f
commit
6ba57d701f
71
src/backend/accounts/auth.ts
Normal file
71
src/backend/accounts/auth.ts
Normal file
|
@ -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,
|
||||
});
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
import classNames from "classnames";
|
||||
import { ReactNode } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
import { IconPatch } from "@/components/buttons/IconPatch";
|
||||
import { Icons } from "@/components/Icon";
|
||||
import { Lightbar } from "@/components/utils/Lightbar";
|
||||
import { useAuth } from "@/hooks/useAuth";
|
||||
import { BlurEllipsis } from "@/pages/layouts/SubPageLayout";
|
||||
import { conf } from "@/setup/config";
|
||||
import { useBannerSize } from "@/stores/banner";
|
||||
|
@ -12,7 +12,6 @@ import { useBannerSize } from "@/stores/banner";
|
|||
import { BrandPill } from "./BrandPill";
|
||||
|
||||
export interface NavigationProps {
|
||||
children?: ReactNode;
|
||||
bg?: boolean;
|
||||
noLightbar?: boolean;
|
||||
doBackground?: boolean;
|
||||
|
@ -20,6 +19,7 @@ export interface NavigationProps {
|
|||
|
||||
export function Navigation(props: NavigationProps) {
|
||||
const bannerHeight = useBannerSize();
|
||||
const { loggedIn } = useAuth();
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -60,26 +60,30 @@ export function Navigation(props: NavigationProps) {
|
|||
<div className="absolute -bottom-24 h-24 w-full bg-gradient-to-b from-background-main to-transparent" />
|
||||
</div>
|
||||
<div className="pointer-events-auto px-7 py-5 relative flex flex-1 items-center space-x-3">
|
||||
<Link className="block" to="/">
|
||||
<BrandPill clickable />
|
||||
</Link>
|
||||
<a
|
||||
href={conf().DISCORD_LINK}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="text-xl text-white"
|
||||
>
|
||||
<IconPatch icon={Icons.DISCORD} clickable downsized />
|
||||
</a>
|
||||
<a
|
||||
href={conf().GITHUB_LINK}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="text-xl text-white"
|
||||
>
|
||||
<IconPatch icon={Icons.GITHUB} clickable downsized />
|
||||
</a>
|
||||
{props.children}
|
||||
<div className="flex items-center flex-1">
|
||||
<Link className="block" to="/">
|
||||
<BrandPill clickable />
|
||||
</Link>
|
||||
<a
|
||||
href={conf().DISCORD_LINK}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="text-xl text-white"
|
||||
>
|
||||
<IconPatch icon={Icons.DISCORD} clickable downsized />
|
||||
</a>
|
||||
<a
|
||||
href={conf().GITHUB_LINK}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="text-xl text-white"
|
||||
>
|
||||
<IconPatch icon={Icons.GITHUB} clickable downsized />
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<p>User: {JSON.stringify(loggedIn)}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
54
src/hooks/useAuth.ts
Normal file
54
src/hooks/useAuth.ts
Normal file
|
@ -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,
|
||||
};
|
||||
}
|
|
@ -7,6 +7,7 @@ interface Config {
|
|||
TMDB_READ_API_KEY: string;
|
||||
CORS_PROXY_URL: string;
|
||||
NORMAL_ROUTER: boolean;
|
||||
BACKEND_URL: string;
|
||||
}
|
||||
|
||||
export interface RuntimeConfig {
|
||||
|
@ -16,6 +17,7 @@ export interface RuntimeConfig {
|
|||
TMDB_READ_API_KEY: string;
|
||||
NORMAL_ROUTER: boolean;
|
||||
PROXY_URLS: string[];
|
||||
BACKEND_URL: string;
|
||||
}
|
||||
|
||||
const env: Record<keyof Config, undefined | string> = {
|
||||
|
@ -25,6 +27,7 @@ const env: Record<keyof Config, undefined | string> = {
|
|||
DISCORD_LINK: undefined,
|
||||
CORS_PROXY_URL: import.meta.env.VITE_CORS_PROXY_URL,
|
||||
NORMAL_ROUTER: import.meta.env.VITE_NORMAL_ROUTER,
|
||||
BACKEND_URL: import.meta.env.VITE_BACKEND_URL,
|
||||
};
|
||||
|
||||
// loads from different locations, in order: environment (VITE_{KEY}), window (public/config.js)
|
||||
|
@ -44,6 +47,7 @@ export function conf(): RuntimeConfig {
|
|||
APP_VERSION,
|
||||
GITHUB_LINK,
|
||||
DISCORD_LINK,
|
||||
BACKEND_URL: getKey("BACKEND_URL"),
|
||||
TMDB_READ_API_KEY: getKey("TMDB_READ_API_KEY"),
|
||||
PROXY_URLS: getKey("CORS_PROXY_URL")
|
||||
.split(",")
|
||||
|
|
58
src/stores/auth/index.ts
Normal file
58
src/stores/auth/index.ts
Normal file
|
@ -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 a new issue