mirror of
https://github.com/sourcebot-dev/sourcebot.git
synced 2025-12-11 20:05:25 +00:00
properly handle sso idps from config
This commit is contained in:
parent
e047eb06b9
commit
6db7aa37dd
9 changed files with 47 additions and 47 deletions
|
|
@ -8,10 +8,10 @@ import { CredentialsForm } from "@/app/login/components/credentialsForm";
|
|||
import { DividerSet } from "@/app/components/dividerSet";
|
||||
import { ProviderButton } from "@/app/components/providerButton";
|
||||
import { AuthSecurityNotice } from "@/app/components/authSecurityNotice";
|
||||
import type { AuthProvider } from "@/lib/authProviders";
|
||||
import type { IdentityProviderMetadata } from "@/lib/authProviders";
|
||||
|
||||
interface AuthMethodSelectorProps {
|
||||
providers: AuthProvider[];
|
||||
providers: IdentityProviderMetadata[];
|
||||
callbackUrl?: string;
|
||||
context: "login" | "signup";
|
||||
onProviderClick?: (providerId: string) => void;
|
||||
|
|
@ -35,11 +35,11 @@ export const AuthMethodSelector = ({
|
|||
}, [callbackUrl, onProviderClick]);
|
||||
|
||||
// Separate OAuth providers from special auth methods
|
||||
const oauthProviders = providers.filter(p =>
|
||||
const oauthProviders = providers.filter(p => p.purpose === "sso" &&
|
||||
!["credentials", "nodemailer"].includes(p.id)
|
||||
);
|
||||
const hasCredentials = providers.some(p => p.id === "credentials");
|
||||
const hasMagicLink = providers.some(p => p.id === "nodemailer");
|
||||
const hasCredentials = providers.some(p => p.purpose === "sso" && p.id === "credentials");
|
||||
const hasMagicLink = providers.some(p => p.purpose === "sso" && p.id === "nodemailer");
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|||
import { SourcebotLogo } from "@/app/components/sourcebotLogo";
|
||||
import { AuthMethodSelector } from "@/app/components/authMethodSelector";
|
||||
import { LogoutEscapeHatch } from "@/app/components/logoutEscapeHatch";
|
||||
import { getAuthProviders } from "@/lib/authProviders";
|
||||
import { getIdentityProviderMetadata, IdentityProviderMetadata } from "@/lib/authProviders";
|
||||
import { JoinOrganizationCard } from "@/app/components/joinOrganizationCard";
|
||||
|
||||
interface InvitePageProps {
|
||||
|
|
@ -30,7 +30,7 @@ export default async function InvitePage(props: InvitePageProps) {
|
|||
|
||||
const session = await auth();
|
||||
if (!session) {
|
||||
const providers = getAuthProviders();
|
||||
const providers = getIdentityProviderMetadata();
|
||||
return <WelcomeCard inviteLinkId={inviteLinkId} providers={providers} />;
|
||||
}
|
||||
|
||||
|
|
@ -57,7 +57,7 @@ export default async function InvitePage(props: InvitePageProps) {
|
|||
);
|
||||
}
|
||||
|
||||
function WelcomeCard({ inviteLinkId, providers }: { inviteLinkId: string; providers: import("@/lib/authProviders").AuthProvider[] }) {
|
||||
function WelcomeCard({ inviteLinkId, providers }: { inviteLinkId: string; providers: IdentityProviderMetadata[] }) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-[var(--background)] to-[var(--accent)]/30 flex items-center justify-center p-6">
|
||||
<Card className="w-full max-w-md">
|
||||
|
|
|
|||
|
|
@ -6,12 +6,12 @@ import { SourcebotLogo } from "@/app/components/sourcebotLogo";
|
|||
import { AuthMethodSelector } from "@/app/components/authMethodSelector";
|
||||
import useCaptureEvent from "@/hooks/useCaptureEvent";
|
||||
import Link from "next/link";
|
||||
import type { AuthProvider } from "@/lib/authProviders";
|
||||
import type { IdentityProviderMetadata } from "@/lib/authProviders";
|
||||
|
||||
interface LoginFormProps {
|
||||
callbackUrl?: string;
|
||||
error?: string;
|
||||
providers: AuthProvider[];
|
||||
providers: IdentityProviderMetadata[];
|
||||
context: "login" | "signup";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { auth } from "@/auth";
|
|||
import { LoginForm } from "./components/loginForm";
|
||||
import { redirect } from "next/navigation";
|
||||
import { Footer } from "@/app/components/footer";
|
||||
import { getAuthProviders } from "@/lib/authProviders";
|
||||
import { getIdentityProviderMetadata } from "@/lib/authProviders";
|
||||
import { getOrgFromDomain } from "@/data/org";
|
||||
import { SINGLE_TENANT_ORG_DOMAIN } from "@/lib/constants";
|
||||
|
||||
|
|
@ -25,7 +25,7 @@ export default async function Login(props: LoginProps) {
|
|||
return redirect("/onboard");
|
||||
}
|
||||
|
||||
const providers = getAuthProviders();
|
||||
const providers = getIdentityProviderMetadata();
|
||||
return (
|
||||
<div className="flex flex-col min-h-screen bg-backgroundSecondary">
|
||||
<div className="flex-1 flex flex-col items-center p-4 sm:p-12 w-full">
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { Button } from "@/components/ui/button"
|
|||
import { AuthMethodSelector } from "@/app/components/authMethodSelector"
|
||||
import { SourcebotLogo } from "@/app/components/sourcebotLogo"
|
||||
import { auth } from "@/auth";
|
||||
import { getAuthProviders } from "@/lib/authProviders";
|
||||
import { getIdentityProviderMetadata } from "@/lib/authProviders";
|
||||
import { OrganizationAccessSettings } from "@/app/components/organizationAccessSettings";
|
||||
import { CompleteOnboardingButton } from "./components/completeOnboardingButton";
|
||||
import { getOrgFromDomain } from "@/data/org";
|
||||
|
|
@ -41,7 +41,7 @@ interface ResourceCard {
|
|||
|
||||
export default async function Onboarding(props: OnboardingProps) {
|
||||
const searchParams = await props.searchParams;
|
||||
const providers = getAuthProviders();
|
||||
const providers = getIdentityProviderMetadata();
|
||||
const org = await getOrgFromDomain(SINGLE_TENANT_ORG_DOMAIN);
|
||||
const session = await auth();
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { LoginForm } from "../login/components/loginForm";
|
|||
import { redirect } from "next/navigation";
|
||||
import { Footer } from "@/app/components/footer";
|
||||
import { createLogger } from "@sourcebot/logger";
|
||||
import { getAuthProviders } from "@/lib/authProviders";
|
||||
import { getIdentityProviderMetadata } from "@/lib/authProviders";
|
||||
import { getOrgFromDomain } from "@/data/org";
|
||||
import { SINGLE_TENANT_ORG_DOMAIN } from "@/lib/constants";
|
||||
|
||||
|
|
@ -29,7 +29,7 @@ export default async function Signup(props: LoginProps) {
|
|||
return redirect("/onboard");
|
||||
}
|
||||
|
||||
const providers = getAuthProviders();
|
||||
const providers = getIdentityProviderMetadata();
|
||||
return (
|
||||
<div className="flex flex-col min-h-screen bg-backgroundSecondary">
|
||||
<div className="flex-1 flex flex-col items-center p-4 sm:p-12 w-full">
|
||||
|
|
|
|||
|
|
@ -13,17 +13,22 @@ import { createTransport } from 'nodemailer';
|
|||
import { render } from '@react-email/render';
|
||||
import MagicLinkEmail from './emails/magicLinkEmail';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import { getSSOProviders } from '@/ee/features/sso/sso';
|
||||
import { getEEIdentityProviders } from '@/ee/features/sso/sso';
|
||||
import { hasEntitlement } from '@sourcebot/shared';
|
||||
import { onCreateUser } from '@/lib/authUtils';
|
||||
import { getAuditService } from '@/ee/features/audit/factory';
|
||||
import { SINGLE_TENANT_ORG_ID } from './lib/constants';
|
||||
|
||||
const auditService = getAuditService();
|
||||
const ssoProviders = hasEntitlement("sso") ? await getSSOProviders() : [];
|
||||
const eeIdentityProviders = hasEntitlement("sso") ? await getEEIdentityProviders() : [];
|
||||
|
||||
export const runtime = 'nodejs';
|
||||
|
||||
export type IdentityProvider = {
|
||||
provider: Provider;
|
||||
purpose: "sso" | "integration";
|
||||
}
|
||||
|
||||
declare module 'next-auth' {
|
||||
interface Session {
|
||||
user: {
|
||||
|
|
@ -33,16 +38,16 @@ declare module 'next-auth' {
|
|||
}
|
||||
|
||||
declare module 'next-auth/jwt' {
|
||||
interface JWT {
|
||||
interface JWT {
|
||||
userId: string
|
||||
}
|
||||
}
|
||||
|
||||
export const getProviders = () => {
|
||||
const providers: Provider[] = ssoProviders;
|
||||
const providers: IdentityProvider[] = eeIdentityProviders;
|
||||
|
||||
if (env.SMTP_CONNECTION_URL && env.EMAIL_FROM_ADDRESS && env.AUTH_EMAIL_CODE_LOGIN_ENABLED === 'true') {
|
||||
providers.push(EmailProvider({
|
||||
providers.push({ provider: EmailProvider({
|
||||
server: env.SMTP_CONNECTION_URL,
|
||||
from: env.EMAIL_FROM_ADDRESS,
|
||||
maxAge: 60 * 10,
|
||||
|
|
@ -66,11 +71,11 @@ export const getProviders = () => {
|
|||
throw new Error(`Email(s) (${failed.join(", ")}) could not be sent`);
|
||||
}
|
||||
}
|
||||
}));
|
||||
}), purpose: "sso"});
|
||||
}
|
||||
|
||||
if (env.AUTH_CREDENTIALS_LOGIN_ENABLED === 'true') {
|
||||
providers.push(Credentials({
|
||||
providers.push({ provider: Credentials({
|
||||
credentials: {
|
||||
email: {},
|
||||
password: {}
|
||||
|
|
@ -123,7 +128,7 @@ export const getProviders = () => {
|
|||
};
|
||||
}
|
||||
}
|
||||
}));
|
||||
}), purpose: "sso"});
|
||||
}
|
||||
|
||||
return providers;
|
||||
|
|
@ -193,7 +198,7 @@ export const { handlers, signIn, signOut, auth } = NextAuth({
|
|||
return session;
|
||||
},
|
||||
},
|
||||
providers: getProviders(),
|
||||
providers: getProviders().map((provider) => provider.provider),
|
||||
pages: {
|
||||
signIn: "/login",
|
||||
// We set redirect to false in signInOptions so we can pass the email is as a param
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import type { Provider } from "next-auth/providers";
|
||||
import { env } from "@/env.mjs";
|
||||
import GitHub from "next-auth/providers/github";
|
||||
import Google from "next-auth/providers/google";
|
||||
|
|
@ -14,12 +13,13 @@ import { onCreateUser } from "@/lib/authUtils";
|
|||
import { createLogger } from "@sourcebot/logger";
|
||||
import { hasEntitlement, loadConfig } from "@sourcebot/shared";
|
||||
import { getTokenFromConfig } from "@sourcebot/crypto";
|
||||
import type { IdentityProvider } from "@/auth";
|
||||
import { GCPIAPIdentityProviderConfig, GitHubIdentityProviderConfig, GitLabIdentityProviderConfig, GoogleIdentityProviderConfig, KeycloakIdentityProviderConfig, MicrosoftEntraIDIdentityProviderConfig, OktaIdentityProviderConfig } from "@sourcebot/schemas/v3/index.type";
|
||||
|
||||
const logger = createLogger('web-sso');
|
||||
|
||||
export const getSSOProviders = async (): Promise<Provider[]> => {
|
||||
const providers: Provider[] = [];
|
||||
export const getEEIdentityProviders = async (): Promise<IdentityProvider[]> => {
|
||||
const providers: IdentityProvider[] = [];
|
||||
|
||||
const config = env.CONFIG_PATH ? await loadConfig(env.CONFIG_PATH) : undefined;
|
||||
const identityProviders = config?.identityProviders ?? [];
|
||||
|
|
@ -27,55 +27,49 @@ export const getSSOProviders = async (): Promise<Provider[]> => {
|
|||
for (const identityProvider of identityProviders) {
|
||||
if (identityProvider.provider === "github") {
|
||||
const providerConfig = identityProvider as GitHubIdentityProviderConfig;
|
||||
if (providerConfig.purpose !== "sso") {
|
||||
continue;
|
||||
}
|
||||
const clientId = await getTokenFromConfig(providerConfig.clientId);
|
||||
const clientSecret = await getTokenFromConfig(providerConfig.clientSecret);
|
||||
const baseUrl = providerConfig.baseUrl ? await getTokenFromConfig(providerConfig.baseUrl) : undefined;
|
||||
providers.push(createGitHubProvider(clientId, clientSecret, baseUrl));
|
||||
providers.push({ provider: createGitHubProvider(clientId, clientSecret, baseUrl), purpose: providerConfig.purpose });
|
||||
}
|
||||
if (identityProvider.provider === "gitlab") {
|
||||
const providerConfig = identityProvider as GitLabIdentityProviderConfig;
|
||||
if (providerConfig.purpose !== "sso") {
|
||||
continue;
|
||||
}
|
||||
const clientId = await getTokenFromConfig(providerConfig.clientId);
|
||||
const clientSecret = await getTokenFromConfig(providerConfig.clientSecret);
|
||||
const baseUrl = providerConfig.baseUrl ? await getTokenFromConfig(providerConfig.baseUrl) : undefined;
|
||||
providers.push(createGitLabProvider(clientId, clientSecret, baseUrl));
|
||||
providers.push({ provider: createGitLabProvider(clientId, clientSecret, baseUrl), purpose: providerConfig.purpose });
|
||||
}
|
||||
if (identityProvider.provider === "google") {
|
||||
const providerConfig = identityProvider as GoogleIdentityProviderConfig;
|
||||
const clientId = await getTokenFromConfig(providerConfig.clientId);
|
||||
const clientSecret = await getTokenFromConfig(providerConfig.clientSecret);
|
||||
providers.push(createGoogleProvider(clientId, clientSecret));
|
||||
providers.push({ provider: createGoogleProvider(clientId, clientSecret), purpose: "sso"});
|
||||
}
|
||||
if (identityProvider.provider === "okta") {
|
||||
const providerConfig = identityProvider as OktaIdentityProviderConfig;
|
||||
const clientId = await getTokenFromConfig(providerConfig.clientId);
|
||||
const clientSecret = await getTokenFromConfig(providerConfig.clientSecret);
|
||||
const issuer = await getTokenFromConfig(providerConfig.issuer);
|
||||
providers.push(createOktaProvider(clientId, clientSecret, issuer));
|
||||
providers.push({ provider: createOktaProvider(clientId, clientSecret, issuer), purpose: "sso"});
|
||||
}
|
||||
if (identityProvider.provider === "keycloak") {
|
||||
const providerConfig = identityProvider as KeycloakIdentityProviderConfig;
|
||||
const clientId = await getTokenFromConfig(providerConfig.clientId);
|
||||
const clientSecret = await getTokenFromConfig(providerConfig.clientSecret);
|
||||
const issuer = await getTokenFromConfig(providerConfig.issuer);
|
||||
providers.push(createKeycloakProvider(clientId, clientSecret, issuer));
|
||||
providers.push({ provider: createKeycloakProvider(clientId, clientSecret, issuer), purpose: "sso"});
|
||||
}
|
||||
if (identityProvider.provider === "microsoft-entra-id") {
|
||||
const providerConfig = identityProvider as MicrosoftEntraIDIdentityProviderConfig;
|
||||
const clientId = await getTokenFromConfig(providerConfig.clientId);
|
||||
const clientSecret = await getTokenFromConfig(providerConfig.clientSecret);
|
||||
const issuer = await getTokenFromConfig(providerConfig.issuer);
|
||||
providers.push(createMicrosoftEntraIDProvider(clientId, clientSecret, issuer));
|
||||
providers.push({ provider: createMicrosoftEntraIDProvider(clientId, clientSecret, issuer), purpose: "sso"});
|
||||
}
|
||||
if (identityProvider.provider === "gcp-iap") {
|
||||
const providerConfig = identityProvider as GCPIAPIdentityProviderConfig;
|
||||
const audience = await getTokenFromConfig(providerConfig.audience);
|
||||
providers.push(createGCPIAPProvider(audience));
|
||||
providers.push({ provider: createGCPIAPProvider(audience), purpose: "sso"});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,18 +1,19 @@
|
|||
import { getProviders } from "@/auth";
|
||||
|
||||
export interface AuthProvider {
|
||||
export interface IdentityProviderMetadata {
|
||||
id: string;
|
||||
name: string;
|
||||
purpose: "sso" | "integration";
|
||||
}
|
||||
|
||||
export const getAuthProviders = (): AuthProvider[] => {
|
||||
export const getIdentityProviderMetadata = (): IdentityProviderMetadata[] => {
|
||||
const providers = getProviders();
|
||||
return providers.map((provider) => {
|
||||
if (typeof provider === "function") {
|
||||
const providerInfo = provider();
|
||||
return { id: providerInfo.id, name: providerInfo.name };
|
||||
if (typeof provider.provider === "function") {
|
||||
const providerInfo = provider.provider();
|
||||
return { id: providerInfo.id, name: providerInfo.name, purpose: provider.purpose };
|
||||
} else {
|
||||
return { id: provider.id, name: provider.name };
|
||||
return { id: provider.provider.id, name: provider.provider.name, purpose: provider.purpose };
|
||||
}
|
||||
});
|
||||
};
|
||||
Loading…
Reference in a new issue