refactor: cleanup / pr review
This commit is contained in:
parent
5e4f3fca62
commit
2d2183baaf
10 changed files with 38 additions and 2280 deletions
|
|
@ -27,7 +27,6 @@ import { Label } from "~/client/components/ui/label";
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~/client/components/ui/select";
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~/client/components/ui/select";
|
||||||
import { Switch } from "~/client/components/ui/switch";
|
import { Switch } from "~/client/components/ui/switch";
|
||||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "~/client/components/ui/table";
|
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 { useOrganizationContext } from "~/client/hooks/use-org-context";
|
||||||
import { formatDateWithMonth } from "~/client/lib/datetime";
|
import { formatDateWithMonth } from "~/client/lib/datetime";
|
||||||
import { getOrigin } from "~/client/functions/get-origin";
|
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");
|
toast.success(v.body?.enabled ? "Automatic account linking enabled" : "Automatic account linking disabled");
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
toast.error("Failed to update provider", {
|
toast.error("Failed to update provider", { description: error.message });
|
||||||
description: parseError(error)?.message,
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -68,7 +65,7 @@ export function SsoSettingsSection() {
|
||||||
toast.success("SSO provider deleted");
|
toast.success("SSO provider deleted");
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
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("");
|
setInviteEmail("");
|
||||||
setInviteRole("member");
|
setInviteRole("member");
|
||||||
},
|
},
|
||||||
onError: (error: unknown) => {
|
onError: (error) => {
|
||||||
toast.error("Failed to create invitation", { description: parseError(error)?.message });
|
toast.error("Failed to create invitation", { description: error.message });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -113,8 +110,8 @@ export function SsoSettingsSection() {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success("Invitation cancelled");
|
toast.success("Invitation cancelled");
|
||||||
},
|
},
|
||||||
onError: (error: unknown) => {
|
onError: (error) => {
|
||||||
toast.error("Failed to cancel invitation", { description: parseError(error)?.message });
|
toast.error("Failed to cancel invitation", { description: error.message });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -124,7 +121,7 @@ export function SsoSettingsSection() {
|
||||||
toast.success("Invitation deleted");
|
toast.success("Invitation deleted");
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
toast.error("Failed to delete invitation", { description: parseError(error)?.message });
|
toast.error("Failed to delete invitation", { description: error.message });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -90,8 +90,8 @@ export function CreateSsoProviderPage() {
|
||||||
toast.success("SSO provider registered successfully");
|
toast.success("SSO provider registered successfully");
|
||||||
void navigate({ to: "/settings", search: { tab: "users" } });
|
void navigate({ to: "/settings", search: { tab: "users" } });
|
||||||
},
|
},
|
||||||
onError: (error: unknown) => {
|
onError: (error) => {
|
||||||
toast.error("Failed to register provider", { description: parseError(error)?.message });
|
toast.error("Failed to register provider", { description: error.message });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -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`);
|
|
||||||
|
|
@ -2,15 +2,16 @@ CREATE TABLE `sso_provider` (
|
||||||
`id` text PRIMARY KEY,
|
`id` text PRIMARY KEY,
|
||||||
`provider_id` text NOT NULL,
|
`provider_id` text NOT NULL,
|
||||||
`organization_id` text NOT NULL,
|
`organization_id` text NOT NULL,
|
||||||
`user_id` text NOT NULL,
|
`user_id` text,
|
||||||
`issuer` text NOT NULL,
|
`issuer` text NOT NULL,
|
||||||
`domain` text NOT NULL,
|
`domain` text NOT NULL,
|
||||||
|
`auto_link_matching_emails` integer DEFAULT false NOT NULL,
|
||||||
`oidc_config` text,
|
`oidc_config` text,
|
||||||
`saml_config` text,
|
`saml_config` text,
|
||||||
`created_at` integer DEFAULT (unixepoch() * 1000) NOT NULL,
|
`created_at` integer DEFAULT (unixepoch() * 1000) NOT NULL,
|
||||||
`updated_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_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
|
--> statement-breakpoint
|
||||||
CREATE UNIQUE INDEX `sso_provider_provider_id_uidx` ON `sso_provider` (`provider_id`);--> statement-breakpoint
|
CREATE UNIQUE INDEX `sso_provider_provider_id_uidx` ON `sso_provider` (`provider_id`);--> statement-breakpoint
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"version": "7",
|
"version": "7",
|
||||||
"dialect": "sqlite",
|
"dialect": "sqlite",
|
||||||
"id": "93504601-a3b2-4f09-92ca-16f132bcdd3a",
|
"id": "f1d1baed-4d81-40b5-86fb-56e9c173a27f",
|
||||||
"prevIds": ["ab028cd7-c0cb-44f4-843b-37e95ab3c667"],
|
"prevIds": ["3a308c54-d950-464f-9490-fee06985fbeb"],
|
||||||
"ddl": [
|
"ddl": [
|
||||||
{
|
{
|
||||||
"name": "account",
|
"name": "account",
|
||||||
|
|
@ -1212,7 +1212,7 @@
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
"autoincrement": false,
|
"autoincrement": false,
|
||||||
"default": "true",
|
"default": "false",
|
||||||
"generated": null,
|
"generated": null,
|
||||||
"name": "auto_link_matching_emails",
|
"name": "auto_link_matching_emails",
|
||||||
"entityType": "columns",
|
"entityType": "columns",
|
||||||
|
|
@ -27,24 +27,12 @@ function buildOrgSlug(email: string) {
|
||||||
return `${emailPrefix}-${Math.random().toString(36).slice(-4)}`;
|
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) {
|
async function tryCreateInvitedMembership(userId: string, email: string, ctx: GenericEndpointContext | null) {
|
||||||
logger.debug("Checking for pending invitations for user", userId, email);
|
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) {
|
if (!ssoProvider) {
|
||||||
logger.debug("No SSO provider found in context, skipping invitation check");
|
logger.debug("No SSO provider found in context, skipping invitation check");
|
||||||
return null;
|
return null;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,11 @@ export function normalizeEmail(email: string): string {
|
||||||
return email.trim().toLowerCase();
|
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) {
|
if (ctx.params?.providerId) {
|
||||||
return ctx.params.providerId;
|
return ctx.params.providerId;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,9 @@ export const publicSsoProvidersDto = type({
|
||||||
providers: type({
|
providers: type({
|
||||||
providerId: "string",
|
providerId: "string",
|
||||||
organizationSlug: "string",
|
organizationSlug: "string",
|
||||||
}).array(),
|
})
|
||||||
|
.onUndeclaredKey("delete")
|
||||||
|
.array(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type PublicSsoProvidersDto = typeof publicSsoProvidersDto.infer;
|
export type PublicSsoProvidersDto = typeof publicSsoProvidersDto.infer;
|
||||||
|
|
|
||||||
|
|
@ -130,12 +130,10 @@ export class AuthService {
|
||||||
* Update per-provider auto-linking setting
|
* Update per-provider auto-linking setting
|
||||||
*/
|
*/
|
||||||
async updateSsoProviderAutoLinking(providerId: string, enabled: boolean): Promise<boolean> {
|
async updateSsoProviderAutoLinking(providerId: string, enabled: boolean): Promise<boolean> {
|
||||||
const existingProvider = await db
|
const existingProvider = await db.query.ssoProvider.findFirst({
|
||||||
.select({ id: ssoProvider.id })
|
where: { providerId },
|
||||||
.from(ssoProvider)
|
columns: { id: true },
|
||||||
.where(eq(ssoProvider.providerId, providerId))
|
});
|
||||||
.limit(1)
|
|
||||||
.then((result) => result[0]);
|
|
||||||
|
|
||||||
if (!existingProvider) {
|
if (!existingProvider) {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -159,10 +157,10 @@ export class AuthService {
|
||||||
async getUserAccounts(userIds: string[]): Promise<Record<string, { id: string; providerId: string }[]>> {
|
async getUserAccounts(userIds: string[]): Promise<Record<string, { id: string; providerId: string }[]>> {
|
||||||
if (userIds.length === 0) return {};
|
if (userIds.length === 0) return {};
|
||||||
|
|
||||||
const accounts = await db
|
const accounts = await db.query.account.findMany({
|
||||||
.select({ id: account.id, providerId: account.providerId, userId: account.userId })
|
where: { userId: { in: userIds } },
|
||||||
.from(account)
|
columns: { id: true, providerId: true, userId: true },
|
||||||
.where(inArray(account.userId, userIds));
|
});
|
||||||
|
|
||||||
const grouped: Record<string, { id: string; providerId: string }[]> = {};
|
const grouped: Record<string, { id: string; providerId: string }[]> = {};
|
||||||
for (const row of accounts) {
|
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
|
* Delete a single account for a user, refusing if it is the last one
|
||||||
*/
|
*/
|
||||||
async deleteUserAccount(userId: string, accountId: string): Promise<{ lastAccount: boolean }> {
|
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) {
|
if (userAccounts.length <= 1) {
|
||||||
return { lastAccount: true };
|
return { lastAccount: true };
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue