mirror of
https://github.com/RayLabsHQ/gitea-mirror.git
synced 2025-12-06 11:36:44 +03:00
More fixes in SSO
This commit is contained in:
10
drizzle/0002_bored_captain_cross.sql
Normal file
10
drizzle/0002_bored_captain_cross.sql
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
CREATE TABLE `verifications` (
|
||||||
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
|
`identifier` text NOT NULL,
|
||||||
|
`value` text NOT NULL,
|
||||||
|
`expires_at` integer NOT NULL,
|
||||||
|
`created_at` integer DEFAULT (unixepoch()) NOT NULL,
|
||||||
|
`updated_at` integer DEFAULT (unixepoch()) NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE INDEX `idx_verifications_identifier` ON `verifications` (`identifier`);
|
||||||
1784
drizzle/meta/0002_snapshot.json
Normal file
1784
drizzle/meta/0002_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -15,6 +15,13 @@
|
|||||||
"when": 1752173351102,
|
"when": 1752173351102,
|
||||||
"tag": "0001_polite_exodus",
|
"tag": "0001_polite_exodus",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 2,
|
||||||
|
"version": "6",
|
||||||
|
"when": 1753539600567,
|
||||||
|
"tag": "0002_bored_captain_cross",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -6,11 +6,9 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/com
|
|||||||
import { Switch } from '@/components/ui/switch';
|
import { Switch } from '@/components/ui/switch';
|
||||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
|
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
|
||||||
import { apiRequest, showErrorToast } from '@/lib/utils';
|
import { apiRequest, showErrorToast } from '@/lib/utils';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { Plus, Trash2, ExternalLink, Loader2, AlertCircle, Shield, Info } from 'lucide-react';
|
import { Plus, Trash2, Loader2, AlertCircle, Shield } from 'lucide-react';
|
||||||
import { Separator } from '@/components/ui/separator';
|
|
||||||
import { Skeleton } from '../ui/skeleton';
|
import { Skeleton } from '../ui/skeleton';
|
||||||
import { Badge } from '../ui/badge';
|
import { Badge } from '../ui/badge';
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||||
@@ -102,7 +100,7 @@ export function SSOSettings() {
|
|||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
const [providersRes, headerAuthStatus] = await Promise.all([
|
const [providersRes, headerAuthStatus] = await Promise.all([
|
||||||
apiRequest<SSOProvider[]>('/sso/providers'),
|
apiRequest<SSOProvider[] | { providers: SSOProvider[] }>('/sso/providers'),
|
||||||
apiRequest<{ enabled: boolean }>('/auth/header-status').catch(() => ({ enabled: false }))
|
apiRequest<{ enabled: boolean }>('/auth/header-status').catch(() => ({ enabled: false }))
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -164,7 +162,7 @@ export function SSOSettings() {
|
|||||||
requestData.jwksEndpoint = providerForm.jwksEndpoint;
|
requestData.jwksEndpoint = providerForm.jwksEndpoint;
|
||||||
requestData.userInfoEndpoint = providerForm.userInfoEndpoint;
|
requestData.userInfoEndpoint = providerForm.userInfoEndpoint;
|
||||||
requestData.discoveryEndpoint = providerForm.discoveryEndpoint;
|
requestData.discoveryEndpoint = providerForm.discoveryEndpoint;
|
||||||
requestData.scopes = providerForm.scopes;
|
// Don't send scopes - let the backend handle provider-specific defaults
|
||||||
requestData.pkce = providerForm.pkce;
|
requestData.pkce = providerForm.pkce;
|
||||||
} else {
|
} else {
|
||||||
requestData.entryPoint = providerForm.entryPoint;
|
requestData.entryPoint = providerForm.entryPoint;
|
||||||
@@ -224,10 +222,6 @@ export function SSOSettings() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const copyToClipboard = (text: string) => {
|
|
||||||
navigator.clipboard.writeText(text);
|
|
||||||
toast.success('Copied to clipboard');
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
@@ -448,7 +442,14 @@ export function SSOSettings() {
|
|||||||
<Alert>
|
<Alert>
|
||||||
<AlertCircle className="h-4 w-4" />
|
<AlertCircle className="h-4 w-4" />
|
||||||
<AlertDescription>
|
<AlertDescription>
|
||||||
Redirect URL: {window.location.origin}/api/auth/sso/callback/{providerForm.providerId || '{provider-id}'}
|
<div className="space-y-2">
|
||||||
|
<p>Redirect URL: {window.location.origin}/api/auth/sso/callback/{providerForm.providerId || '{provider-id}'}</p>
|
||||||
|
{providerForm.issuer.includes('google.com') && (
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
Note: Google doesn't support the "offline_access" scope. The system will automatically use appropriate scopes.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</AlertDescription>
|
</AlertDescription>
|
||||||
</Alert>
|
</Alert>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ export {
|
|||||||
sessions,
|
sessions,
|
||||||
accounts,
|
accounts,
|
||||||
verificationTokens,
|
verificationTokens,
|
||||||
|
verifications,
|
||||||
oauthApplications,
|
oauthApplications,
|
||||||
oauthAccessTokens,
|
oauthAccessTokens,
|
||||||
oauthConsent,
|
oauthConsent,
|
||||||
|
|||||||
@@ -518,6 +518,24 @@ export const verificationTokens = sqliteTable("verification_tokens", {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Verifications table (for Better Auth)
|
||||||
|
export const verifications = sqliteTable("verifications", {
|
||||||
|
id: text("id").primaryKey(),
|
||||||
|
identifier: text("identifier").notNull(),
|
||||||
|
value: text("value").notNull(),
|
||||||
|
expiresAt: integer("expires_at", { mode: "timestamp" }).notNull(),
|
||||||
|
createdAt: integer("created_at", { mode: "timestamp" })
|
||||||
|
.notNull()
|
||||||
|
.default(sql`(unixepoch())`),
|
||||||
|
updatedAt: integer("updated_at", { mode: "timestamp" })
|
||||||
|
.notNull()
|
||||||
|
.default(sql`(unixepoch())`),
|
||||||
|
}, (table) => {
|
||||||
|
return {
|
||||||
|
identifierIdx: index("idx_verifications_identifier").on(table.identifier),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
// ===== OIDC Provider Tables =====
|
// ===== OIDC Provider Tables =====
|
||||||
|
|
||||||
// OAuth Applications table
|
// OAuth Applications table
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ export async function POST(context: APIContext) {
|
|||||||
jwksEndpoint,
|
jwksEndpoint,
|
||||||
discoveryEndpoint,
|
discoveryEndpoint,
|
||||||
userInfoEndpoint,
|
userInfoEndpoint,
|
||||||
scopes = ["openid", "email", "profile"],
|
scopes,
|
||||||
pkce = true,
|
pkce = true,
|
||||||
mapping = {
|
mapping = {
|
||||||
id: "sub",
|
id: "sub",
|
||||||
@@ -88,6 +88,23 @@ export async function POST(context: APIContext) {
|
|||||||
}
|
}
|
||||||
} = body;
|
} = body;
|
||||||
|
|
||||||
|
// Handle provider-specific scope defaults
|
||||||
|
let finalScopes = scopes;
|
||||||
|
if (!finalScopes) {
|
||||||
|
// Check if this is a Google provider
|
||||||
|
const isGoogle = issuer.includes('google.com') ||
|
||||||
|
issuer.includes('googleapis.com') ||
|
||||||
|
domain.includes('google.com');
|
||||||
|
|
||||||
|
if (isGoogle) {
|
||||||
|
// Google doesn't support offline_access scope
|
||||||
|
finalScopes = ["openid", "email", "profile"];
|
||||||
|
} else {
|
||||||
|
// Default scopes for other providers
|
||||||
|
finalScopes = ["openid", "email", "profile", "offline_access"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
registrationBody.oidcConfig = {
|
registrationBody.oidcConfig = {
|
||||||
clientId,
|
clientId,
|
||||||
clientSecret,
|
clientSecret,
|
||||||
@@ -96,7 +113,7 @@ export async function POST(context: APIContext) {
|
|||||||
jwksEndpoint,
|
jwksEndpoint,
|
||||||
discoveryEndpoint,
|
discoveryEndpoint,
|
||||||
userInfoEndpoint,
|
userInfoEndpoint,
|
||||||
scopes,
|
scopes: finalScopes,
|
||||||
pkce,
|
pkce,
|
||||||
};
|
};
|
||||||
registrationBody.mapping = mapping;
|
registrationBody.mapping = mapping;
|
||||||
|
|||||||
@@ -13,7 +13,14 @@ export async function GET(context: APIContext) {
|
|||||||
|
|
||||||
const providers = await db.select().from(ssoProviders);
|
const providers = await db.select().from(ssoProviders);
|
||||||
|
|
||||||
return new Response(JSON.stringify(providers), {
|
// Parse JSON fields before sending
|
||||||
|
const formattedProviders = providers.map(provider => ({
|
||||||
|
...provider,
|
||||||
|
oidcConfig: provider.oidcConfig ? JSON.parse(provider.oidcConfig) : undefined,
|
||||||
|
samlConfig: provider.samlConfig ? JSON.parse(provider.samlConfig) : undefined,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return new Response(JSON.stringify(formattedProviders), {
|
||||||
status: 200,
|
status: 200,
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
});
|
});
|
||||||
@@ -102,7 +109,14 @@ export async function POST(context: APIContext) {
|
|||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
return new Response(JSON.stringify(newProvider), {
|
// Parse JSON fields before sending
|
||||||
|
const formattedProvider = {
|
||||||
|
...newProvider,
|
||||||
|
oidcConfig: newProvider.oidcConfig ? JSON.parse(newProvider.oidcConfig) : undefined,
|
||||||
|
samlConfig: newProvider.samlConfig ? JSON.parse(newProvider.samlConfig) : undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
return new Response(JSON.stringify(formattedProvider), {
|
||||||
status: 201,
|
status: 201,
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user