Use Azure auth for no good reason
This commit is contained in:
@@ -1,11 +1,15 @@
|
||||
import cors from "@elysiajs/cors";
|
||||
import { staticPlugin } from "@elysiajs/static";
|
||||
import { swagger } from "@elysiajs/swagger";
|
||||
import { AttachmentId, PieceId, RepertoireId, RequestId, SessionId, Sha256_Bin, Sha256_Hex, UserId } from "common";
|
||||
import { AttachmentId, PieceId, RepertoireId, RequestId, SessionId, Sha256_Bin, Sha256_Hex } from "common";
|
||||
import { Effect, Option, pipe } from "effect";
|
||||
import { Elysia, error, t } from "elysia";
|
||||
import { sql } from "kysely";
|
||||
import { CLIENT_ID, EXTERNAL_OAUTH_CONFIGURATION, getUser, INTERNAL_OAUTH_CONFIGURATION, makeAuthorizationUrl, REDIRECT_URI, revalidateTokens } from "./auth";
|
||||
import * as Db from "./database";
|
||||
import * as Model from "./model";
|
||||
import { DbFromInstance } from "./services/db";
|
||||
import { SessionFromValue } from "./services/session";
|
||||
|
||||
const app = new Elysia()
|
||||
|
||||
@@ -36,37 +40,85 @@ const app = new Elysia()
|
||||
.where(sql`datetime()`, ">=", "expiresAt")
|
||||
.execute();
|
||||
|
||||
const sessionId = cookie.sessionId.value;
|
||||
if (sessionId === undefined) {
|
||||
return { user: null };
|
||||
}
|
||||
const sessionId = (cookie.sessionId.value as SessionId | undefined) ?? Db.generateSessionId();
|
||||
|
||||
const res = await db
|
||||
const expiresAt = new Date().getTime() + 604800000;
|
||||
cookie.sessionId.set({
|
||||
value: sessionId,
|
||||
expires: new Date(expiresAt),
|
||||
httpOnly: true,
|
||||
sameSite: "none",
|
||||
secure: true,
|
||||
});
|
||||
|
||||
const returning = [
|
||||
"sessionId",
|
||||
"accessToken",
|
||||
"codeVerifier",
|
||||
"external",
|
||||
"idToken",
|
||||
"refreshToken",
|
||||
"state",
|
||||
] as const;
|
||||
|
||||
let session = await db
|
||||
.updateTable("Session")
|
||||
.set({ expiresAt: sql`datetime('now', '+7 days') ` })
|
||||
.where("sessionId", "=", SessionId(sessionId))
|
||||
.returning(["userId"])
|
||||
.where("sessionId", "=", sessionId)
|
||||
.returning(returning)
|
||||
.executeTakeFirst();
|
||||
|
||||
if (res === undefined) {
|
||||
return { user: null };
|
||||
if (session === undefined) {
|
||||
session = await db
|
||||
.insertInto("Session")
|
||||
.values({ sessionId, expiresAt: sql`datetime('now', '+7 days')` })
|
||||
.returning(returning)
|
||||
.executeTakeFirstOrThrow();
|
||||
}
|
||||
|
||||
const user = await db
|
||||
.selectFrom("User")
|
||||
.select(["userId", "username", "admin"])
|
||||
.where("userId", "=", res.userId)
|
||||
.executeTakeFirst();
|
||||
const { accessToken, idToken, refreshToken, roles, userId } = await pipe(
|
||||
{
|
||||
accessToken: Option.fromNullable(session.accessToken),
|
||||
idToken: Option.fromNullable(session.idToken),
|
||||
refreshToken: Option.fromNullable(session.refreshToken),
|
||||
external: Boolean(session.external),
|
||||
},
|
||||
revalidateTokens,
|
||||
Effect.runPromise,
|
||||
);
|
||||
|
||||
if (user === undefined) {
|
||||
return { user: null };
|
||||
}
|
||||
await db
|
||||
.updateTable("Session")
|
||||
.set({
|
||||
accessToken: pipe(
|
||||
accessToken,
|
||||
Option.map((at) => at.token),
|
||||
Option.getOrNull,
|
||||
),
|
||||
idToken: pipe(
|
||||
idToken,
|
||||
Option.map((it) => it.token),
|
||||
Option.getOrNull,
|
||||
),
|
||||
refreshToken: Option.getOrNull(refreshToken),
|
||||
})
|
||||
.execute();
|
||||
|
||||
return {
|
||||
user: {
|
||||
userId: user.userId,
|
||||
username: user.username,
|
||||
admin: user.admin !== 0,
|
||||
session: {
|
||||
sessionId: session.sessionId,
|
||||
accessToken,
|
||||
idToken,
|
||||
refreshToken,
|
||||
roles,
|
||||
userId,
|
||||
codeVerifier: Option.fromNullable(session.codeVerifier),
|
||||
external: pipe(
|
||||
session.external,
|
||||
Option.fromNullable,
|
||||
Option.map((e) => e !== 0),
|
||||
),
|
||||
state: Option.fromNullable(session.state),
|
||||
},
|
||||
};
|
||||
})
|
||||
@@ -100,66 +152,87 @@ const app = new Elysia()
|
||||
|
||||
// --- MARK: AUTHENTICATION --------------------------------------------
|
||||
|
||||
.get("/me", ({ user }) => {
|
||||
|
||||
if (user === null) {
|
||||
return error("Unauthorized", "Session invalid or expired");
|
||||
}
|
||||
|
||||
return user;
|
||||
.get("/me", ({ session: { idToken, roles } }) => {
|
||||
return Option.match(idToken, {
|
||||
onNone: () => error("Unauthorized", "Session invalid or expired"),
|
||||
onSome: ({ payload: { oid, name } }) => ({
|
||||
userId: oid,
|
||||
username: name,
|
||||
roles: roles as string[],
|
||||
}),
|
||||
});
|
||||
}, {
|
||||
response: {
|
||||
200: Model.User,
|
||||
200: Model.Me,
|
||||
401: t.Literal("Session invalid or expired"),
|
||||
},
|
||||
})
|
||||
|
||||
.post("/login", async ({ db, body: { username, password }, cookie }) => {
|
||||
.get("/login", async ({ db, query, redirect, session: { sessionId } }) => {
|
||||
|
||||
const user = await db
|
||||
.selectFrom("User")
|
||||
.selectAll()
|
||||
.where("username", "=", username)
|
||||
.executeTakeFirst();
|
||||
const url = await pipe(
|
||||
makeAuthorizationUrl({ external: "external" in query }),
|
||||
Effect.provide([
|
||||
DbFromInstance(db),
|
||||
SessionFromValue(sessionId),
|
||||
]),
|
||||
Effect.runPromise,
|
||||
);
|
||||
|
||||
if (user === undefined) {
|
||||
return error("Unauthorized", "Invalid username or password");
|
||||
}
|
||||
|
||||
const valid = await Bun.password.verify(password, user.password);
|
||||
if (!valid) {
|
||||
return error("Unauthorized", "Invalid username or password");
|
||||
}
|
||||
|
||||
const sessionId = Db.generateSessionId();
|
||||
await db
|
||||
.insertInto("Session")
|
||||
.values({ sessionId, userId: user.userId, expiresAt: sql`datetime('now', '+7 days')` })
|
||||
.execute();
|
||||
|
||||
const expiresAt = new Date().getTime() + 604800000;
|
||||
cookie.sessionId.set({
|
||||
value: sessionId,
|
||||
expires: new Date(expiresAt),
|
||||
httpOnly: true,
|
||||
sameSite: "none",
|
||||
secure: true,
|
||||
});
|
||||
|
||||
return {
|
||||
userId: user.userId,
|
||||
username: user.username,
|
||||
admin: user.admin !== 0,
|
||||
sessionId,
|
||||
};
|
||||
return redirect(url, 302);
|
||||
}, {
|
||||
body: t.Object({
|
||||
username: t.String({ minLength: 1 }),
|
||||
password: t.String({ minLength: 1 }),
|
||||
}),
|
||||
response: {
|
||||
200: Model.User,
|
||||
401: t.Literal("Invalid username or password"),
|
||||
302: t.Void(),
|
||||
},
|
||||
})
|
||||
|
||||
.post("/login", async ({ db, request, redirect, session: { sessionId, external, codeVerifier } }) => {
|
||||
const data = await request.formData();
|
||||
|
||||
const code = data.get("code") as string | null;
|
||||
const state = data.get("state") as string | null;
|
||||
|
||||
if (code !== null && state !== null && Option.isSome(external) && Option.isSome(codeVerifier)) {
|
||||
const { tokenEndpoint } = external.value ? EXTERNAL_OAUTH_CONFIGURATION : INTERNAL_OAUTH_CONFIGURATION;
|
||||
|
||||
const res = await fetch(tokenEndpoint, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
"client_id": CLIENT_ID,
|
||||
"code": code,
|
||||
"redirect_uri": REDIRECT_URI,
|
||||
"grant_type": "authorization_code",
|
||||
"code_verifier": codeVerifier.value,
|
||||
"client_secret": process.env.CLIENT_SECRET!,
|
||||
}).toString(),
|
||||
});
|
||||
|
||||
const {
|
||||
access_token: accessToken,
|
||||
refresh_token: refreshToken,
|
||||
id_token: idToken,
|
||||
} = await res.json() as { access_token: string, refresh_token: string, id_token: string };
|
||||
|
||||
await db
|
||||
.updateTable("Session")
|
||||
.set({
|
||||
accessToken,
|
||||
refreshToken,
|
||||
idToken,
|
||||
codeVerifier: null,
|
||||
state: null,
|
||||
})
|
||||
.where("sessionId", "=", sessionId)
|
||||
.execute();
|
||||
}
|
||||
|
||||
return redirect(process.env.NODE_ENV === "production" ? "https://music.renati.me/" : "http://localhost:5173/", 303);
|
||||
}, {
|
||||
response: {
|
||||
303: t.Void(),
|
||||
},
|
||||
})
|
||||
|
||||
@@ -180,142 +253,29 @@ const app = new Elysia()
|
||||
.where("sessionId", "=", SessionId(sessionId))
|
||||
.execute();
|
||||
}, {
|
||||
|
||||
response: {
|
||||
204: t.Void(),
|
||||
},
|
||||
})
|
||||
|
||||
.post("/change-password", async ({ db, body: { username, currentPassword, newPassword }, set }) => {
|
||||
|
||||
const user = await db
|
||||
.selectFrom("User")
|
||||
.selectAll()
|
||||
.where("username", "=", username)
|
||||
.executeTakeFirst();
|
||||
|
||||
if (user === undefined) {
|
||||
return error("Unauthorized", "Invalid username or password");
|
||||
}
|
||||
|
||||
const valid = await Bun.password.verify(currentPassword, user.password);
|
||||
if (!valid) {
|
||||
return error("Unauthorized", "Invalid username or password");
|
||||
}
|
||||
|
||||
const password = await Bun.password.hash(newPassword);
|
||||
|
||||
const res = await db
|
||||
.updateTable("User")
|
||||
.set({ password })
|
||||
.where("username", "=", username)
|
||||
.returningAll()
|
||||
.execute();
|
||||
|
||||
if (res.length === 0) {
|
||||
return error("Unauthorized", "Invalid username or password");
|
||||
}
|
||||
|
||||
set.status = "No Content";
|
||||
}, {
|
||||
body: t.Object({
|
||||
username: t.String({ minLength: 1 }),
|
||||
currentPassword: t.String({ minLength: 1 }),
|
||||
newPassword: t.String({ minLength: 1 }),
|
||||
}),
|
||||
response: {
|
||||
204: t.Void(),
|
||||
401: t.Literal("Invalid username or password"),
|
||||
},
|
||||
})
|
||||
|
||||
// --- MARK: USER MANAGEMENT -------------------------------------------
|
||||
|
||||
.post("/user", async ({ db, body: { username, password, admin }, user }) => {
|
||||
.get("/user/:userId", async ({ params: { userId }, session: { accessToken } }) => {
|
||||
|
||||
if (user === null) {
|
||||
if (Option.isNone(accessToken)) {
|
||||
return error("Unauthorized", "Session invalid or expired");
|
||||
}
|
||||
|
||||
if (!user.admin) {
|
||||
return error("Forbidden", "Operation allowed only for admin users");
|
||||
}
|
||||
const res = await pipe(
|
||||
{ accessToken: accessToken.value.token, userId },
|
||||
getUser,
|
||||
Effect.runPromise,
|
||||
);
|
||||
|
||||
const userId = UserId(Bun.randomUUIDv7());
|
||||
|
||||
const newUser = await db
|
||||
.insertInto("User")
|
||||
.values({
|
||||
userId,
|
||||
username,
|
||||
password: await Bun.password.hash(password),
|
||||
admin: admin ? 1 : 0,
|
||||
})
|
||||
.returningAll()
|
||||
.executeTakeFirstOrThrow();
|
||||
|
||||
return {
|
||||
userId: newUser.userId,
|
||||
username: newUser.username,
|
||||
admin: newUser.admin !== 0,
|
||||
};
|
||||
}, {
|
||||
body: Model.User_Post,
|
||||
response: {
|
||||
200: Model.User,
|
||||
401: t.Literal("Session invalid or expired"),
|
||||
403: t.Literal("Operation allowed only for admin users"),
|
||||
},
|
||||
})
|
||||
|
||||
.get("/user", async ({ db, query, user }) => {
|
||||
|
||||
if (user === null) {
|
||||
return error("Unauthorized", "Session invalid or expired");
|
||||
}
|
||||
|
||||
let q = db
|
||||
.selectFrom("User")
|
||||
.select("userId")
|
||||
.orderBy(["username", "userId"])
|
||||
.offset(query.offset ?? 0)
|
||||
.limit(query.limit ?? 100);
|
||||
|
||||
if (query.username !== undefined) {
|
||||
q = q.where("username", "like", "%" + query.username + "%");
|
||||
}
|
||||
|
||||
const res = await q.execute();
|
||||
return res.map(({ userId }) => userId);
|
||||
}, {
|
||||
query: Model.User_Query,
|
||||
response: {
|
||||
200: t.Array(Model.UserId),
|
||||
401: t.Literal("Session invalid or expired"),
|
||||
},
|
||||
})
|
||||
|
||||
.get("/user/:userId", async ({ db, params: { userId }, user }) => {
|
||||
|
||||
if (user === null) {
|
||||
return error("Unauthorized", "Session invalid or expired");
|
||||
}
|
||||
|
||||
const res = await db
|
||||
.selectFrom("User")
|
||||
.select(["userId", "username", "admin"])
|
||||
.where("userId", "=", userId)
|
||||
.executeTakeFirst();
|
||||
|
||||
if (res === undefined) {
|
||||
return error("Not Found", undefined);
|
||||
}
|
||||
|
||||
return {
|
||||
userId: res.userId,
|
||||
username: res.username,
|
||||
admin: res.admin !== 0,
|
||||
};
|
||||
return Option.match(res, {
|
||||
onNone: () => error("Not Found", undefined),
|
||||
onSome: ({ displayName }) => ({ userId, displayName }),
|
||||
});
|
||||
}, {
|
||||
params: t.Object({
|
||||
userId: Model.UserId,
|
||||
@@ -327,95 +287,23 @@ const app = new Elysia()
|
||||
},
|
||||
})
|
||||
|
||||
.patch("/user/:userId", async ({ db, body: { username, password, admin }, params: { userId }, user }) => {
|
||||
|
||||
if (user === null) {
|
||||
return error("Unauthorized", "Session invalid or expired");
|
||||
}
|
||||
|
||||
if (!user.admin) {
|
||||
return error("Forbidden", "Operation allowed only for admin users");
|
||||
}
|
||||
|
||||
const res = await db
|
||||
.updateTable("User")
|
||||
.set({
|
||||
...(username !== undefined ? { username } : undefined),
|
||||
...(password !== undefined ? { password: await Bun.password.hash(password) } : undefined),
|
||||
...(admin !== undefined ? { admin: admin ? 1 : 0 } : undefined),
|
||||
})
|
||||
.where("userId", "=", userId)
|
||||
.returning(["userId", "username", "admin"])
|
||||
.executeTakeFirst();
|
||||
|
||||
if (res === undefined) {
|
||||
return error("Not Found", undefined);
|
||||
}
|
||||
|
||||
return {
|
||||
userId: res.userId,
|
||||
username: res.username,
|
||||
admin: res.admin !== 0,
|
||||
};
|
||||
}, {
|
||||
body: Model.User_Patch,
|
||||
params: t.Object({
|
||||
userId: Model.UserId,
|
||||
}),
|
||||
response: {
|
||||
200: Model.User,
|
||||
401: t.Literal("Session invalid or expired"),
|
||||
403: t.Literal("Operation allowed only for admin users"),
|
||||
404: t.Void(),
|
||||
},
|
||||
})
|
||||
|
||||
.delete("/user/:userId", async ({ db, params: { userId }, set, user }) => {
|
||||
|
||||
if (user === null) {
|
||||
return error("Unauthorized", "Session invalid or expired");
|
||||
}
|
||||
|
||||
if (!user.admin) {
|
||||
return error("Forbidden", "Operation allowed only for admin users");
|
||||
}
|
||||
|
||||
const res = await db
|
||||
.deleteFrom("User")
|
||||
.where("userId", "=", userId)
|
||||
.returningAll()
|
||||
.execute();
|
||||
|
||||
if (res.length === 0) {
|
||||
return error("Not Found");
|
||||
}
|
||||
|
||||
set.status = "No Content";
|
||||
}, {
|
||||
params: t.Object({
|
||||
userId: Model.UserId,
|
||||
}),
|
||||
response: {
|
||||
204: t.Void(),
|
||||
401: t.Literal("Session invalid or expired"),
|
||||
403: t.Literal("Operation allowed only for admin users"),
|
||||
404: t.Void(),
|
||||
},
|
||||
})
|
||||
|
||||
// --- MARK: PIECE CRUD ------------------------------------------------
|
||||
|
||||
.post("/piece", async ({ db, body: { name, composer, lyricist, arranger }, user }) => {
|
||||
.post("/piece", async ({ db, body: { name, composer, lyricist, arranger }, session: { idToken } }) => {
|
||||
|
||||
if (user === null) {
|
||||
if (Option.isNone(idToken)) {
|
||||
return error("Unauthorized", "Session invalid or expired");
|
||||
}
|
||||
|
||||
if (!idToken.value.payload.roles.includes("Editor")) {
|
||||
return error("Forbidden", "Must be an Editor");
|
||||
}
|
||||
|
||||
const pieceId = PieceId(Bun.randomUUIDv7());
|
||||
|
||||
const res = await db
|
||||
.insertInto("Piece")
|
||||
.values({ pieceId, name, composer, lyricist, arranger, createdBy: user.userId, createdAt: sql`datetime()` })
|
||||
.values({ pieceId, name, composer, lyricist, arranger, createdBy: idToken.value.payload.oid, createdAt: sql`datetime()` })
|
||||
.returningAll()
|
||||
.executeTakeFirstOrThrow();
|
||||
|
||||
@@ -428,12 +316,13 @@ const app = new Elysia()
|
||||
response: {
|
||||
200: Model.Piece,
|
||||
401: t.Literal("Session invalid or expired"),
|
||||
403: t.Literal("Must be an Editor"),
|
||||
},
|
||||
})
|
||||
|
||||
.get("/piece", async ({ db, query, user }) => {
|
||||
.get("/piece", async ({ db, query, session: { idToken } }) => {
|
||||
|
||||
if (user === null) {
|
||||
if (Option.isNone(idToken)) {
|
||||
return error("Unauthorized", "Session invalid or expired");
|
||||
}
|
||||
|
||||
@@ -466,9 +355,9 @@ const app = new Elysia()
|
||||
},
|
||||
})
|
||||
|
||||
.get("/piece/:pieceId", async ({ db, params: { pieceId }, user }) => {
|
||||
.get("/piece/:pieceId", async ({ db, params: { pieceId }, session: { idToken } }) => {
|
||||
|
||||
if (user === null) {
|
||||
if (Option.isNone(idToken)) {
|
||||
return error("Unauthorized", "Session invalid or expired");
|
||||
}
|
||||
|
||||
@@ -506,15 +395,19 @@ const app = new Elysia()
|
||||
},
|
||||
})
|
||||
|
||||
.put("/piece/:pieceId", async ({ db, body: { name, composer, lyricist, arranger }, params: { pieceId }, user }) => {
|
||||
.put("/piece/:pieceId", async ({ db, body: { name, composer, lyricist, arranger }, params: { pieceId }, session: { idToken } }) => {
|
||||
|
||||
if (user === null) {
|
||||
if (Option.isNone(idToken)) {
|
||||
return error("Unauthorized", "Session invalid or expired");
|
||||
}
|
||||
|
||||
if (!idToken.value.payload.roles.includes("Editor")) {
|
||||
return error("Forbidden", "Must be an Editor");
|
||||
}
|
||||
|
||||
const res = await db
|
||||
.updateTable("Piece")
|
||||
.set({ name, composer, lyricist, arranger, modifiedBy: user.userId, modifiedAt: sql`datetime()` })
|
||||
.set({ name, composer, lyricist, arranger, modifiedBy: idToken.value.payload.oid, modifiedAt: sql`datetime()` })
|
||||
.where("pieceId", "=", pieceId)
|
||||
.returningAll()
|
||||
.execute();
|
||||
@@ -549,16 +442,21 @@ const app = new Elysia()
|
||||
response: {
|
||||
200: Model.Piece,
|
||||
401: t.Literal("Session invalid or expired"),
|
||||
403: t.Literal("Must be an Editor"),
|
||||
404: t.Void(),
|
||||
},
|
||||
})
|
||||
|
||||
.delete("/piece/:pieceId", async ({ db, params: { pieceId }, set, user }) => {
|
||||
.delete("/piece/:pieceId", async ({ db, params: { pieceId }, set, session: { idToken } }) => {
|
||||
|
||||
if (user === null) {
|
||||
if (Option.isNone(idToken)) {
|
||||
return error("Unauthorized", "Session invalid or expired");
|
||||
}
|
||||
|
||||
if (!idToken.value.payload.roles.includes("Editor")) {
|
||||
return error("Forbidden", "Must be an Editor");
|
||||
}
|
||||
|
||||
const res = await db
|
||||
.deleteFrom("Piece")
|
||||
.where("pieceId", "=", pieceId)
|
||||
@@ -583,12 +481,16 @@ const app = new Elysia()
|
||||
|
||||
// --- MARK: ATTACHMENT CRUD -------------------------------------------
|
||||
|
||||
.post("/piece/:pieceId/attachment", async ({ db, body: { filename, mediaType, data }, params: { pieceId }, user }) => {
|
||||
.post("/piece/:pieceId/attachment", async ({ db, body: { filename, mediaType, data }, params: { pieceId }, session: { idToken } }) => {
|
||||
|
||||
if (user === null) {
|
||||
if (Option.isNone(idToken)) {
|
||||
return error("Unauthorized", "Session invalid or expired");
|
||||
}
|
||||
|
||||
if (!idToken.value.payload.roles.includes("Editor")) {
|
||||
return error("Forbidden", "Must be an Editor");
|
||||
}
|
||||
|
||||
const attachmentId = AttachmentId(Bun.randomUUIDv7());
|
||||
const dataArray = new Uint8Array(await data.arrayBuffer());
|
||||
|
||||
@@ -603,7 +505,7 @@ const app = new Elysia()
|
||||
|
||||
const res = await db
|
||||
.insertInto("Attachment")
|
||||
.values({ attachmentId, pieceId, sha256, filename, mediaType, createdBy: user.userId, createdAt: sql`datetime()` })
|
||||
.values({ attachmentId, pieceId, sha256, filename, mediaType, createdBy: idToken.value.payload.oid, createdAt: sql`datetime()` })
|
||||
.returningAll()
|
||||
.executeTakeFirstOrThrow();
|
||||
|
||||
@@ -623,6 +525,7 @@ const app = new Elysia()
|
||||
response: {
|
||||
200: Model.Attachment,
|
||||
401: t.Literal("Session invalid or expired"),
|
||||
403: t.Literal("Must be an Editor"),
|
||||
},
|
||||
})
|
||||
|
||||
@@ -633,9 +536,9 @@ const app = new Elysia()
|
||||
* hash). Thus, we reflect the ownership in the URLs.
|
||||
*/
|
||||
|
||||
.get("/piece/:pieceId/attachment/:attachmentId", async ({ db, params: { pieceId, attachmentId }, user, set }) => {
|
||||
.get("/piece/:pieceId/attachment/:attachmentId", async ({ db, params: { pieceId, attachmentId }, session: { idToken }, set }) => {
|
||||
|
||||
if (user === null) {
|
||||
if (Option.isNone(idToken)) {
|
||||
return error("Unauthorized", "Session invalid or expired");
|
||||
}
|
||||
|
||||
@@ -668,15 +571,19 @@ const app = new Elysia()
|
||||
},
|
||||
})
|
||||
|
||||
.put("/piece/:pieceId/attachment/:attachmentId", async ({ db, body: { filename }, params: { pieceId, attachmentId }, user }) => {
|
||||
.put("/piece/:pieceId/attachment/:attachmentId", async ({ db, body: { filename }, params: { pieceId, attachmentId }, session: { idToken } }) => {
|
||||
|
||||
if (user === null) {
|
||||
if (Option.isNone(idToken)) {
|
||||
return error("Unauthorized", "Session invalid or expired");
|
||||
}
|
||||
|
||||
if (!idToken.value.payload.roles.includes("Editor")) {
|
||||
return error("Forbidden", "Must be an Editor");
|
||||
}
|
||||
|
||||
const res = await db
|
||||
.updateTable("Attachment")
|
||||
.set({ filename, modifiedBy: user.userId, modifiedAt: sql`datetime()` })
|
||||
.set({ filename, modifiedBy: idToken.value.payload.oid, modifiedAt: sql`datetime()` })
|
||||
.where((eb) => eb.and([
|
||||
eb("pieceId", "=", pieceId),
|
||||
eb("attachmentId", "=", attachmentId),
|
||||
@@ -703,16 +610,21 @@ const app = new Elysia()
|
||||
response: {
|
||||
200: Model.Attachment,
|
||||
401: t.Literal("Session invalid or expired"),
|
||||
403: t.Literal("Must be an Editor"),
|
||||
404: t.Void(),
|
||||
},
|
||||
})
|
||||
|
||||
.delete("/piece/:pieceId/attachment/:attachmentId", async ({ db, params: { pieceId, attachmentId }, set, user }) => {
|
||||
.delete("/piece/:pieceId/attachment/:attachmentId", async ({ db, params: { pieceId, attachmentId }, set, session: { idToken } }) => {
|
||||
|
||||
if (user === null) {
|
||||
if (Option.isNone(idToken)) {
|
||||
return error("Unauthorized", "Session invalid or expired");
|
||||
}
|
||||
|
||||
if (!idToken.value.payload.roles.includes("Editor")) {
|
||||
return error("Forbidden", "Must be an Editor");
|
||||
}
|
||||
|
||||
const res = await db
|
||||
.deleteFrom("Attachment")
|
||||
.where((eb) => eb.and([
|
||||
@@ -735,23 +647,28 @@ const app = new Elysia()
|
||||
response: {
|
||||
204: t.Void(),
|
||||
401: t.Literal("Session invalid or expired"),
|
||||
403: t.Literal("Must be an Editor"),
|
||||
404: t.Void(),
|
||||
},
|
||||
})
|
||||
|
||||
// --- MARK: REPERTOIRE CRUD -------------------------------------------
|
||||
|
||||
.post("/repertoire", async ({ db, body: { name, entries }, user }) => {
|
||||
.post("/repertoire", async ({ db, body: { name, entries }, session: { idToken } }) => {
|
||||
|
||||
if (user === null) {
|
||||
if (Option.isNone(idToken)) {
|
||||
return error("Unauthorized", "Session invalid or expired");
|
||||
}
|
||||
|
||||
if (!idToken.value.payload.roles.includes("Editor")) {
|
||||
return error("Forbidden", "Must be an Editor");
|
||||
}
|
||||
|
||||
const repertoireId = RepertoireId(Bun.randomUUIDv7());
|
||||
|
||||
const repertoire = await db
|
||||
.insertInto("Repertoire")
|
||||
.values({ repertoireId, name, createdBy: user.userId, createdAt: sql`datetime()` })
|
||||
.values({ repertoireId, name, createdBy: idToken.value.payload.oid, createdAt: sql`datetime()` })
|
||||
.returningAll()
|
||||
.executeTakeFirstOrThrow();
|
||||
|
||||
@@ -779,12 +696,13 @@ const app = new Elysia()
|
||||
}),
|
||||
response: {
|
||||
401: t.Literal("Session invalid or expired"),
|
||||
403: t.Literal("Must be an Editor"),
|
||||
},
|
||||
})
|
||||
|
||||
.get("/repertoire", async ({ db, query, user }) => {
|
||||
.get("/repertoire", async ({ db, query, session: { idToken } }) => {
|
||||
|
||||
if (user === null) {
|
||||
if (Option.isNone(idToken)) {
|
||||
return error("Unauthorized", "Session invalid or expired");
|
||||
}
|
||||
|
||||
@@ -809,9 +727,9 @@ const app = new Elysia()
|
||||
},
|
||||
})
|
||||
|
||||
.get("/repertoire/:repertoireId", async ({ db, params: { repertoireId }, user }) => {
|
||||
.get("/repertoire/:repertoireId", async ({ db, params: { repertoireId }, session: { idToken } }) => {
|
||||
|
||||
if (user === null) {
|
||||
if (Option.isNone(idToken)) {
|
||||
return error("Unauthorized", "Session invalid or expired");
|
||||
}
|
||||
|
||||
@@ -847,15 +765,19 @@ const app = new Elysia()
|
||||
},
|
||||
})
|
||||
|
||||
.put("/repertoire/:repertoireId", async ({ db, body: { name, entries }, params: { repertoireId }, user }) => {
|
||||
.put("/repertoire/:repertoireId", async ({ db, body: { name, entries }, params: { repertoireId }, session: { idToken } }) => {
|
||||
|
||||
if (user === null) {
|
||||
if (Option.isNone(idToken)) {
|
||||
return error("Unauthorized", "Session invalid or expired");
|
||||
}
|
||||
|
||||
if (!idToken.value.payload.roles.includes("Editor")) {
|
||||
return error("Forbidden", "Must be an Editor");
|
||||
}
|
||||
|
||||
const res = await db
|
||||
.updateTable("Repertoire")
|
||||
.set({ name, modifiedBy: user.userId, modifiedAt: sql`datetime()` })
|
||||
.set({ name, modifiedBy: idToken.value.payload.oid, modifiedAt: sql`datetime()` })
|
||||
.where("repertoireId", "=", repertoireId)
|
||||
.returningAll()
|
||||
.execute();
|
||||
@@ -893,16 +815,21 @@ const app = new Elysia()
|
||||
response: {
|
||||
200: Model.Repertoire,
|
||||
401: t.Literal("Session invalid or expired"),
|
||||
403: t.Literal("Must be an Editor"),
|
||||
404: t.Void(),
|
||||
},
|
||||
})
|
||||
|
||||
.delete("/repertoire/:repertoireId", async ({ db, params: { repertoireId }, set, user }) => {
|
||||
.delete("/repertoire/:repertoireId", async ({ db, params: { repertoireId }, set, session: { idToken } }) => {
|
||||
|
||||
if (user === null) {
|
||||
if (Option.isNone(idToken)) {
|
||||
return error("Unauthorized", "Session invalid or expired");
|
||||
}
|
||||
|
||||
if (!idToken.value.payload.roles.includes("Editor")) {
|
||||
return error("Forbidden", "Must be an Editor");
|
||||
}
|
||||
|
||||
const res = await db
|
||||
.deleteFrom("Repertoire")
|
||||
.where("repertoireId", "=", repertoireId)
|
||||
@@ -921,6 +848,7 @@ const app = new Elysia()
|
||||
response: {
|
||||
204: t.Void(),
|
||||
401: t.Literal("Session invalid or expired"),
|
||||
403: t.Literal("Must be an Editor"),
|
||||
404: t.Void(),
|
||||
},
|
||||
// eslint-disable-next-line @stylistic/comma-dangle -- a comma would confuse the TS compiler here
|
||||
|
||||
Reference in New Issue
Block a user