Use Azure auth for no good reason

This commit is contained in:
2025-03-26 19:42:26 +01:00
parent 52933e617a
commit cec7d47c9e
17 changed files with 635 additions and 615 deletions

View File

@@ -1,5 +1,4 @@
import { Home } from "@/routes/Home";
import { Login } from "@/routes/Login";
import { Piece } from "@/routes/Piece";
import { Pieces } from "@/routes/Pieces";
import { Repertoire } from "@/routes/Repertoire";
@@ -63,10 +62,6 @@ const router = createBrowserRouter([
},
],
},
{
path: "/login",
Component: Login,
},
], {
future: {
v7_fetcherPersist: true,

View File

@@ -5,8 +5,7 @@ import { client, mapResponse } from "./client";
export interface User {
readonly userId: UserId;
readonly username: string;
readonly admin: boolean;
readonly displayName: string;
}
export interface SystemInformation {
@@ -108,7 +107,7 @@ export const denormalizeRepertoire = ({
);
const UserSemaphore = Effect.unsafeMakeSemaphore(1);
const CacheSemaphore = Effect.unsafeMakeSemaphore(4);
const PieceSemaphore = Effect.unsafeMakeSemaphore(4);
const RepertoireSemaphore = Effect.unsafeMakeSemaphore(1);
export const userLookup = (userId: UserId) => pipe(
@@ -123,7 +122,7 @@ export const pieceLookup = (pieceId: PieceId) => pipe(
Effect.flatMap(mapResponse),
Effect.flatMap(denormalizePiece),
Effect.map((x): Piece => x), // safely coerce to interface
CacheSemaphore.withPermits(1),
PieceSemaphore.withPermits(1),
);
export const repertoireLookup = (repertoireId: RepertoireId) => pipe(

View File

@@ -1,3 +1,4 @@
import { API_URL_PREFIX } from "@/client";
import { mapProp, Update, Updater, useStore } from "@/hooks/useStore";
import { Treaty } from "@elysiajs/eden";
import { Effect, Fiber, pipe } from "effect";
@@ -57,8 +58,7 @@ export function useLoading<R extends Record<number, unknown>>(fn: () => Promise<
if (error !== null) {
if (error.status === 401) {
setUser(null);
navigate("/login");
window.location.href = `${API_URL_PREFIX}/api/v1/login`;
return;
}

View File

@@ -13,31 +13,17 @@ export namespace Store {
export interface User {
readonly userId: UserId;
readonly username: string;
readonly admin: boolean;
readonly roles: readonly string[];
}
}
export interface Store {
readonly loginUsername: string;
readonly loginPassword: string;
readonly user: Store.User | null;
readonly setLoginUsername: Updater<string>;
readonly setLoginPassword: Updater<string>;
readonly setUser: Updater<Store.User | null>;
}
let store: Store = Object.freeze<Store>({
loginUsername: "",
loginPassword: "",
user: null,
setLoginUsername: (action) => set(mapProp("loginUsername", action)),
setLoginPassword: (action) => set(mapProp("loginPassword", action)),
setUser: (action) => set(mapProp("user", action)),
});

View File

@@ -1,95 +0,0 @@
import { client } from "@/client";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { useStore } from "@/hooks/useStore";
import { Loader2 } from "lucide-react";
import { FormEventHandler, useId, useState } from "react";
import { useNavigate } from "react-router-dom";
export function Login() {
const navigate = useNavigate();
const loginUsername = useStore(state => state.loginUsername);
const loginPassword = useStore(state => state.loginPassword);
const setLoginUsername = useStore(state => state.setLoginUsername);
const setLoginPassword = useStore(state => state.setLoginPassword);
const setUser = useStore(state => state.setUser);
const usernameId = useId();
const passwordId = useId();
const [isLoading, setIsLoading] = useState(false);
const onSubmit: FormEventHandler<HTMLFormElement> = async (e) => {
e.preventDefault();
try {
setIsLoading(true);
const { data, error } = await client.login.post({
username: loginUsername,
password: loginPassword,
});
if (error !== null) {
console.error(error.value);
return;
}
setLoginUsername("");
setLoginPassword("");
setUser(data);
navigate("/");
} finally {
setIsLoading(false);
}
};
return (
<div className="w-full h-full flex items-center justify-center">
<form onSubmit={onSubmit}>
<Card>
<CardHeader>
<CardTitle>Repozytorium muzyczne</CardTitle>
<CardDescription>Zaloguj się, aby kontynuować</CardDescription>
</CardHeader>
<CardContent>
<Label htmlFor={usernameId}>Nazwa użytkownika</Label>
<Input
id={usernameId}
className="w-[32ch]"
type="text"
autoComplete="username"
value={loginUsername}
autoFocus
required
onChange={(e) => setLoginUsername(e.target.value)}
/>
<Label htmlFor={passwordId}>Hasło</Label>
<Input
id={passwordId}
className="w-[32ch]"
type="password"
autoComplete="current-password"
value={loginPassword}
required
onChange={(e) => setLoginPassword(e.target.value)}
/>
</CardContent>
<CardFooter>
<Button type="submit" className="w-full" disabled={isLoading}>
{isLoading && <Loader2 className="animate-spin" />}
Zaloguj się
</Button>
</CardFooter>
</Card>
</form>
</div>
);
}

View File

@@ -1,15 +1,13 @@
import { client } from "@/client";
import { API_URL_PREFIX, client } from "@/client";
import { Button, buttonVariants } from "@/components/ui/button";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
import { useStore } from "@/hooks/useStore";
import { LogOut, Settings, User } from "lucide-react";
import { Settings, User } from "lucide-react";
import { useEffect } from "react";
import { Link, Outlet, useNavigate } from "react-router-dom";
import { Link, Outlet } from "react-router-dom";
export function Root() {
const navigate = useNavigate();
const user = useStore(state => state.user);
const setUser = useStore(state => state.setUser);
@@ -19,7 +17,7 @@ export function Root() {
const { data, error } = await client.me.get();
if (error !== null) {
navigate("/login");
window.location.href = `${API_URL_PREFIX}/api/v1/login`;
return;
}
@@ -29,17 +27,6 @@ export function Root() {
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(() => void init(), []);
const onLogoutClick = async () => {
const { error } = await client.logout.post();
if (error !== null) {
console.error(error.value);
}
setUser(null);
navigate("/login");
};
if (user === null) {
return (
<div className="w-full h-full overflow-hidden flex items-center justify-center">
@@ -66,9 +53,6 @@ export function Root() {
<Settings />Ustawienia
</Link>
</DropdownMenuItem>
<DropdownMenuItem onClick={onLogoutClick}>
<LogOut />Wyloguj się
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>

View File

@@ -1,100 +1,6 @@
import { client } from "@/client";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { useStore } from "@/hooks/useStore";
import { Label } from "@radix-ui/react-label";
import { Loader2 } from "lucide-react";
import { FormEventHandler, useId, useState } from "react";
export function Settings() {
return (
<div className="p-4 overflow-y-auto grow flex flex-wrap items-start gap-4">
<PasswordChangeCard />
</div>
);
}
function PasswordChangeCard() {
const [currentPassword, setCurrentPassword] = useState("");
const [newPassword1, setNewPassword1] = useState("");
const [newPassword2, setNewPassword2] = useState("");
const currentPasswordId = useId();
const newPassword1Id = useId();
const newPassword2Id = useId();
const [isLoading, setIsLoading] = useState(false);
const user = useStore(store => store.user);
const onSubmit: FormEventHandler<HTMLFormElement> = async (e) => {
e.preventDefault();
try {
setIsLoading(true);
const { error } = await client["change-password"].post({
username: user!.username,
currentPassword,
newPassword: newPassword1,
});
if (error !== null) {
console.error(error.value);
return;
}
} finally {
setIsLoading(false);
}
};
return (
<form onSubmit={onSubmit}>
<Card>
<CardHeader>
<CardTitle>Zmiana hasła</CardTitle>
</CardHeader>
<CardContent>
<Label htmlFor={currentPasswordId}>Obecne hasło</Label>
<Input
id={currentPasswordId}
className="w-[32ch]"
type="password"
autoComplete="current-password"
value={currentPassword}
required
onChange={(e) => setCurrentPassword(e.target.value)}
/>
<Label htmlFor={currentPasswordId}>Nowe hasło</Label>
<Input
id={newPassword1Id}
className="w-[32ch]"
type="password"
autoComplete="new-password"
value={newPassword1}
required
onChange={(e) => setNewPassword1(e.target.value)}
/>
<Label htmlFor={currentPasswordId}>Potwierdź hasło</Label>
<Input
id={newPassword2Id}
className="w-[32ch]"
type="password"
autoComplete="new-password"
value={newPassword2}
required
onChange={(e) => setNewPassword2(e.target.value)}
/>
</CardContent>
<CardFooter className="flex-row justify-end">
<Button type="submit" disabled={isLoading || newPassword1 !== newPassword2}>
{isLoading && <Loader2 className="animate-spin" />}
Zmień hasło
</Button>
</CardFooter>
</Card>
</form>
);
}

View File

@@ -36,7 +36,7 @@ export function created({ createdAt, createdBy }: SystemInformation): ReactNode
if (Option.isSome(createdBy)) {
nodes.push(<br />);
nodes.push(`przez ${createdBy.value.username}`);
nodes.push(`przez ${createdBy.value.displayName}`);
}
return nodes;
@@ -48,7 +48,7 @@ export function modified({ modifiedAt, modifiedBy }: SystemInformation): ReactNo
if (Option.isNone(modifiedBy)) {
return "\u2014";
} else {
return `przez ${modifiedBy.value.username}`;
return `przez ${modifiedBy.value.displayName}`;
}
}
@@ -56,7 +56,7 @@ export function modified({ modifiedAt, modifiedBy }: SystemInformation): ReactNo
if (Option.isSome(modifiedBy)) {
nodes.push(<br />);
nodes.push(`przez ${modifiedBy.value.username}`);
nodes.push(`przez ${modifiedBy.value.displayName}`);
}
return nodes;