refactor: cleanup / pr review

This commit is contained in:
Nicolas Meienberger 2026-02-23 21:06:07 +01:00
parent 5e4f3fca62
commit 2d2183baaf
10 changed files with 38 additions and 2280 deletions

View file

@ -27,7 +27,6 @@ import { Label } from "~/client/components/ui/label";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~/client/components/ui/select";
import { Switch } from "~/client/components/ui/switch";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "~/client/components/ui/table";
import { parseError } from "~/client/lib/errors";
import { useOrganizationContext } from "~/client/hooks/use-org-context";
import { formatDateWithMonth } from "~/client/lib/datetime";
import { getOrigin } from "~/client/functions/get-origin";
@ -56,9 +55,7 @@ export function SsoSettingsSection() {
toast.success(v.body?.enabled ? "Automatic account linking enabled" : "Automatic account linking disabled");
},
onError: (error) => {
toast.error("Failed to update provider", {
description: parseError(error)?.message,
});
toast.error("Failed to update provider", { description: error.message });
},
});
@ -68,7 +65,7 @@ export function SsoSettingsSection() {
toast.success("SSO provider deleted");
},
onError: (error) => {
toast.error("Failed to delete provider", { description: parseError(error)?.message });
toast.error("Failed to delete provider", { description: error.message });
},
});
@ -98,8 +95,8 @@ export function SsoSettingsSection() {
setInviteEmail("");
setInviteRole("member");
},
onError: (error: unknown) => {
toast.error("Failed to create invitation", { description: parseError(error)?.message });
onError: (error) => {
toast.error("Failed to create invitation", { description: error.message });
},
});
@ -113,8 +110,8 @@ export function SsoSettingsSection() {
onSuccess: () => {
toast.success("Invitation cancelled");
},
onError: (error: unknown) => {
toast.error("Failed to cancel invitation", { description: parseError(error)?.message });
onError: (error) => {
toast.error("Failed to cancel invitation", { description: error.message });
},
});
@ -124,7 +121,7 @@ export function SsoSettingsSection() {
toast.success("Invitation deleted");
},
onError: (error) => {
toast.error("Failed to delete invitation", { description: parseError(error)?.message });
toast.error("Failed to delete invitation", { description: error.message });
},
});

View file

@ -90,8 +90,8 @@ export function CreateSsoProviderPage() {
toast.success("SSO provider registered successfully");
void navigate({ to: "/settings", search: { tab: "users" } });
},
onError: (error: unknown) => {
toast.error("Failed to register provider", { description: parseError(error)?.message });
onError: (error) => {
toast.error("Failed to register provider", { description: error.message });
},
});

File diff suppressed because it is too large Load diff

View file

@ -1,25 +0,0 @@
ALTER TABLE `sso_provider` ADD `auto_link_matching_emails` integer DEFAULT true NOT NULL;--> statement-breakpoint
PRAGMA foreign_keys=OFF;--> statement-breakpoint
CREATE TABLE `__new_sso_provider` (
`id` text PRIMARY KEY,
`provider_id` text NOT NULL,
`organization_id` text NOT NULL,
`user_id` text,
`issuer` text NOT NULL,
`domain` text NOT NULL,
`auto_link_matching_emails` integer DEFAULT true NOT NULL,
`oidc_config` text,
`saml_config` text,
`created_at` integer DEFAULT (unixepoch() * 1000) NOT NULL,
`updated_at` integer DEFAULT (unixepoch() * 1000) NOT NULL,
CONSTRAINT `fk_sso_provider_organization_id_organization_id_fk` FOREIGN KEY (`organization_id`) REFERENCES `organization`(`id`) ON DELETE CASCADE,
CONSTRAINT `fk_sso_provider_user_id_users_table_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users_table`(`id`) ON DELETE SET NULL
);
--> statement-breakpoint
INSERT INTO `__new_sso_provider`(`id`, `provider_id`, `organization_id`, `user_id`, `issuer`, `domain`, `oidc_config`, `saml_config`, `created_at`, `updated_at`) SELECT `id`, `provider_id`, `organization_id`, `user_id`, `issuer`, `domain`, `oidc_config`, `saml_config`, `created_at`, `updated_at` FROM `sso_provider`;--> statement-breakpoint
DROP TABLE `sso_provider`;--> statement-breakpoint
ALTER TABLE `__new_sso_provider` RENAME TO `sso_provider`;--> statement-breakpoint
PRAGMA foreign_keys=ON;--> statement-breakpoint
CREATE UNIQUE INDEX `sso_provider_provider_id_uidx` ON `sso_provider` (`provider_id`);--> statement-breakpoint
CREATE UNIQUE INDEX `sso_provider_organization_id_uidx` ON `sso_provider` (`organization_id`);--> statement-breakpoint
CREATE INDEX `sso_provider_domain_idx` ON `sso_provider` (`domain`);

View file

@ -2,15 +2,16 @@ CREATE TABLE `sso_provider` (
`id` text PRIMARY KEY,
`provider_id` text NOT NULL,
`organization_id` text NOT NULL,
`user_id` text NOT NULL,
`user_id` text,
`issuer` text NOT NULL,
`domain` text NOT NULL,
`auto_link_matching_emails` integer DEFAULT false NOT NULL,
`oidc_config` text,
`saml_config` text,
`created_at` integer DEFAULT (unixepoch() * 1000) NOT NULL,
`updated_at` integer DEFAULT (unixepoch() * 1000) NOT NULL,
CONSTRAINT `fk_sso_provider_organization_id_organization_id_fk` FOREIGN KEY (`organization_id`) REFERENCES `organization`(`id`) ON DELETE CASCADE,
CONSTRAINT `fk_sso_provider_user_id_users_table_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users_table`(`id`) ON DELETE CASCADE
CONSTRAINT `fk_sso_provider_user_id_users_table_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users_table`(`id`) ON DELETE SET NULL
);
--> statement-breakpoint
CREATE UNIQUE INDEX `sso_provider_provider_id_uidx` ON `sso_provider` (`provider_id`);--> statement-breakpoint

View file

@ -1,8 +1,8 @@
{
"version": "7",
"dialect": "sqlite",
"id": "93504601-a3b2-4f09-92ca-16f132bcdd3a",
"prevIds": ["ab028cd7-c0cb-44f4-843b-37e95ab3c667"],
"id": "f1d1baed-4d81-40b5-86fb-56e9c173a27f",
"prevIds": ["3a308c54-d950-464f-9490-fee06985fbeb"],
"ddl": [
{
"name": "account",
@ -1212,7 +1212,7 @@
"type": "integer",
"notNull": true,
"autoincrement": false,
"default": "true",
"default": "false",
"generated": null,
"name": "auto_link_matching_emails",
"entityType": "columns",

View file

@ -27,24 +27,12 @@ function buildOrgSlug(email: string) {
return `${emailPrefix}-${Math.random().toString(36).slice(-4)}`;
}
async function getProviderFromContext(ctx: GenericEndpointContext | null) {
if (!ctx) {
return null;
}
const providerId = extractProviderIdFromContext(ctx);
if (!providerId) {
return null;
}
const provider = await db.query.ssoProvider.findFirst({ where: { providerId } });
return provider;
}
async function tryCreateInvitedMembership(userId: string, email: string, ctx: GenericEndpointContext | null) {
logger.debug("Checking for pending invitations for user", userId, email);
const ssoProvider = await getProviderFromContext(ctx);
const providerId = extractProviderIdFromContext(ctx);
const ssoProvider = await db.query.ssoProvider.findFirst({ where: { providerId } });
if (!ssoProvider) {
logger.debug("No SSO provider found in context, skipping invitation check");
return null;

View file

@ -4,7 +4,11 @@ export function normalizeEmail(email: string): string {
return email.trim().toLowerCase();
}
export function extractProviderIdFromContext(ctx: GenericEndpointContext): string | null {
export function extractProviderIdFromContext(ctx?: GenericEndpointContext | null) {
if (!ctx) {
return null;
}
if (ctx.params?.providerId) {
return ctx.params.providerId;
}

View file

@ -9,7 +9,9 @@ export const publicSsoProvidersDto = type({
providers: type({
providerId: "string",
organizationSlug: "string",
}).array(),
})
.onUndeclaredKey("delete")
.array(),
});
export type PublicSsoProvidersDto = typeof publicSsoProvidersDto.infer;

View file

@ -130,12 +130,10 @@ export class AuthService {
* Update per-provider auto-linking setting
*/
async updateSsoProviderAutoLinking(providerId: string, enabled: boolean): Promise<boolean> {
const existingProvider = await db
.select({ id: ssoProvider.id })
.from(ssoProvider)
.where(eq(ssoProvider.providerId, providerId))
.limit(1)
.then((result) => result[0]);
const existingProvider = await db.query.ssoProvider.findFirst({
where: { providerId },
columns: { id: true },
});
if (!existingProvider) {
return false;
@ -159,10 +157,10 @@ export class AuthService {
async getUserAccounts(userIds: string[]): Promise<Record<string, { id: string; providerId: string }[]>> {
if (userIds.length === 0) return {};
const accounts = await db
.select({ id: account.id, providerId: account.providerId, userId: account.userId })
.from(account)
.where(inArray(account.userId, userIds));
const accounts = await db.query.account.findMany({
where: { userId: { in: userIds } },
columns: { id: true, providerId: true, userId: true },
});
const grouped: Record<string, { id: string; providerId: string }[]> = {};
for (const row of accounts) {
@ -178,7 +176,10 @@ export class AuthService {
* Delete a single account for a user, refusing if it is the last one
*/
async deleteUserAccount(userId: string, accountId: string): Promise<{ lastAccount: boolean }> {
const userAccounts = await db.select({ id: account.id }).from(account).where(eq(account.userId, userId));
const userAccounts = await db.query.account.findMany({
where: { userId },
columns: { id: true },
});
if (userAccounts.length <= 1) {
return { lastAccount: true };