2024-09-25 12:31:35 +02:00
|
|
|
import { auth } from "@/api";
|
|
|
|
|
import { proxyUrl } from "@/response";
|
|
|
|
|
import { createRoute, z } from "@hono/zod-openapi";
|
2025-03-22 18:04:47 +01:00
|
|
|
import { RolePermission } from "@versia/client/schemas";
|
2024-09-30 14:34:43 +02:00
|
|
|
import { db } from "@versia/kit/db";
|
2024-11-02 00:43:33 +01:00
|
|
|
import { type SQL, eq } from "@versia/kit/drizzle";
|
2025-03-22 18:04:47 +01:00
|
|
|
import { OpenIdAccounts } from "@versia/kit/tables";
|
2024-12-30 18:00:23 +01:00
|
|
|
import { ApiError } from "~/classes/errors/api-error";
|
2024-09-25 12:31:35 +02:00
|
|
|
import type { PluginType } from "~/plugins/openid";
|
|
|
|
|
import { ErrorSchema } from "~/types/api";
|
|
|
|
|
|
2024-11-02 00:43:33 +01:00
|
|
|
export default (plugin: PluginType): void => {
|
2024-09-25 12:31:35 +02:00
|
|
|
plugin.registerRoute("/api/v1/sso", (app) => {
|
|
|
|
|
app.openapi(
|
|
|
|
|
createRoute({
|
|
|
|
|
method: "get",
|
|
|
|
|
path: "/api/v1/sso/{id}",
|
|
|
|
|
summary: "Get linked account",
|
|
|
|
|
middleware: [
|
2024-12-30 19:18:31 +01:00
|
|
|
auth({
|
|
|
|
|
auth: true,
|
2025-03-22 18:04:47 +01:00
|
|
|
permissions: [RolePermission.OAuth],
|
2024-12-30 19:18:31 +01:00
|
|
|
}),
|
2024-09-25 12:31:35 +02:00
|
|
|
plugin.middleware,
|
2024-12-30 18:20:22 +01:00
|
|
|
] as const,
|
2024-09-25 12:31:35 +02:00
|
|
|
request: {
|
|
|
|
|
params: z.object({
|
|
|
|
|
id: z.string(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
responses: {
|
|
|
|
|
200: {
|
|
|
|
|
description: "Linked account",
|
|
|
|
|
content: {
|
|
|
|
|
"application/json": {
|
|
|
|
|
schema: z.object({
|
|
|
|
|
id: z.string(),
|
|
|
|
|
name: z.string(),
|
|
|
|
|
icon: z.string().optional(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
404: {
|
|
|
|
|
description: "Account not found",
|
|
|
|
|
content: {
|
|
|
|
|
"application/json": {
|
|
|
|
|
schema: ErrorSchema,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
async (context) => {
|
|
|
|
|
const { id: issuerId } = context.req.valid("param");
|
|
|
|
|
const { user } = context.get("auth");
|
|
|
|
|
|
|
|
|
|
const issuer = context
|
|
|
|
|
.get("pluginConfig")
|
|
|
|
|
.providers.find((provider) => provider.id === issuerId);
|
|
|
|
|
|
|
|
|
|
if (!issuer) {
|
|
|
|
|
return context.json(
|
|
|
|
|
{
|
|
|
|
|
error: `Issuer with ID ${issuerId} not found in instance's OpenID configuration`,
|
|
|
|
|
},
|
|
|
|
|
404,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const account = await db.query.OpenIdAccounts.findFirst({
|
2024-11-02 00:43:33 +01:00
|
|
|
where: (account, { eq, and }): SQL | undefined =>
|
2024-09-25 12:31:35 +02:00
|
|
|
and(
|
|
|
|
|
eq(account.userId, user.id),
|
|
|
|
|
eq(account.issuerId, issuerId),
|
|
|
|
|
),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!account) {
|
2024-12-30 18:00:23 +01:00
|
|
|
throw new ApiError(
|
2024-09-25 12:31:35 +02:00
|
|
|
404,
|
2024-12-30 18:00:23 +01:00
|
|
|
"Account not found or is not linked to this issuer",
|
2024-09-25 12:31:35 +02:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return context.json(
|
|
|
|
|
{
|
|
|
|
|
id: issuer.id,
|
|
|
|
|
name: issuer.name,
|
2025-02-01 16:32:18 +01:00
|
|
|
icon: issuer.icon
|
|
|
|
|
? proxyUrl(new URL(issuer.icon))
|
|
|
|
|
: undefined,
|
2024-09-25 12:31:35 +02:00
|
|
|
},
|
|
|
|
|
200,
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
app.openapi(
|
|
|
|
|
createRoute({
|
|
|
|
|
method: "delete",
|
|
|
|
|
path: "/api/v1/sso/{id}",
|
|
|
|
|
summary: "Unlink account",
|
|
|
|
|
middleware: [
|
2024-12-30 19:18:31 +01:00
|
|
|
auth({
|
|
|
|
|
auth: true,
|
2025-03-22 18:04:47 +01:00
|
|
|
permissions: [RolePermission.OAuth],
|
2024-12-30 19:18:31 +01:00
|
|
|
}),
|
2024-09-25 12:31:35 +02:00
|
|
|
plugin.middleware,
|
2024-12-30 18:20:22 +01:00
|
|
|
] as const,
|
2024-09-25 12:31:35 +02:00
|
|
|
request: {
|
|
|
|
|
params: z.object({
|
|
|
|
|
id: z.string(),
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
responses: {
|
|
|
|
|
204: {
|
|
|
|
|
description: "Account unlinked",
|
|
|
|
|
},
|
|
|
|
|
404: {
|
|
|
|
|
description: "Account not found",
|
|
|
|
|
content: {
|
|
|
|
|
"application/json": {
|
|
|
|
|
schema: ErrorSchema,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
async (context) => {
|
|
|
|
|
const { id: issuerId } = context.req.valid("param");
|
|
|
|
|
const { user } = context.get("auth");
|
|
|
|
|
|
|
|
|
|
// Check if issuer exists
|
|
|
|
|
const issuer = context
|
|
|
|
|
.get("pluginConfig")
|
|
|
|
|
.providers.find((provider) => provider.id === issuerId);
|
|
|
|
|
|
|
|
|
|
if (!issuer) {
|
|
|
|
|
return context.json(
|
|
|
|
|
{
|
|
|
|
|
error: `Issuer with ID ${issuerId} not found in instance's OpenID configuration`,
|
|
|
|
|
},
|
|
|
|
|
404,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const account = await db.query.OpenIdAccounts.findFirst({
|
2024-11-02 00:43:33 +01:00
|
|
|
where: (account, { eq, and }): SQL | undefined =>
|
2024-09-25 12:31:35 +02:00
|
|
|
and(
|
|
|
|
|
eq(account.userId, user.id),
|
|
|
|
|
eq(account.issuerId, issuerId),
|
|
|
|
|
),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!account) {
|
2024-12-30 18:00:23 +01:00
|
|
|
throw new ApiError(
|
2024-09-25 12:31:35 +02:00
|
|
|
404,
|
2024-12-30 18:00:23 +01:00
|
|
|
"Account not found or is not linked to this issuer",
|
2024-09-25 12:31:35 +02:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await db
|
|
|
|
|
.delete(OpenIdAccounts)
|
|
|
|
|
.where(eq(OpenIdAccounts.id, account.id));
|
|
|
|
|
|
2024-12-30 16:18:28 +01:00
|
|
|
return context.body(null, 204);
|
2024-09-25 12:31:35 +02:00
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
};
|