Merge pull request #116 from zitadel/qa

middleware: public host, instance host
This commit is contained in:
Max Peintner
2024-08-22 08:26:04 +02:00
committed by GitHub
24 changed files with 490 additions and 401 deletions

View File

@@ -1,13 +1,22 @@
import { ProviderSlug } from "@/lib/demos"; import { getBrandingSettings } from "@/lib/zitadel";
import { getBrandingSettings, PROVIDER_NAME_MAPPING } from "@/lib/zitadel";
import DynamicTheme from "@/ui/DynamicTheme"; import DynamicTheme from "@/ui/DynamicTheme";
import { IdentityProviderType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
// This configuration shows the given name in the respective IDP button as fallback
const PROVIDER_NAME_MAPPING: {
[provider: string]: string;
} = {
[IdentityProviderType.GOOGLE]: "Google",
[IdentityProviderType.GITHUB]: "GitHub",
[IdentityProviderType.AZURE_AD]: "Microsoft",
};
export default async function Page({ export default async function Page({
searchParams, searchParams,
params, params,
}: { }: {
searchParams: Record<string | number | symbol, string | undefined>; searchParams: Record<string | number | symbol, string | undefined>;
params: { provider: ProviderSlug }; params: { provider: string };
}) { }) {
const { id, token, authRequestId, organization } = searchParams; const { id, token, authRequestId, organization } = searchParams;
const { provider } = params; const { provider } = params;

View File

@@ -1,142 +1,36 @@
import { ProviderSlug } from "@/lib/demos"; import { PROVIDER_MAPPING } from "@/lib/idp";
import { getBrandingSettings, userService } from "@/lib/zitadel"; import {
addIDPLink,
createUser,
getBrandingSettings,
getIDPByID,
listUsers,
retrieveIDPIntent,
} from "@/lib/zitadel";
import Alert, { AlertType } from "@/ui/Alert"; import Alert, { AlertType } from "@/ui/Alert";
import DynamicTheme from "@/ui/DynamicTheme"; import DynamicTheme from "@/ui/DynamicTheme";
import IdpSignin from "@/ui/IdpSignin"; import IdpSignin from "@/ui/IdpSignin";
import { AddHumanUserRequest } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; import { AutoLinkingOption } from "@zitadel/proto/zitadel/idp/v2/idp_pb";
import { IDPInformation, IDPLink } from "@zitadel/proto/zitadel/user/v2/idp_pb";
import { PartialMessage } from "@zitadel/client";
const PROVIDER_MAPPING: {
[provider: string]: (
rI: IDPInformation,
) => PartialMessage<AddHumanUserRequest>;
} = {
[ProviderSlug.GOOGLE]: (idp: IDPInformation) => {
const rawInfo = idp.rawInformation?.toJson() as {
User: {
email: string;
name?: string;
given_name?: string;
family_name?: string;
};
};
const idpLink: PartialMessage<IDPLink> = {
idpId: idp.idpId,
userId: idp.userId,
userName: idp.userName,
};
const req: PartialMessage<AddHumanUserRequest> = {
username: idp.userName,
email: {
email: rawInfo.User?.email,
verification: { case: "isVerified", value: true },
},
// organisation: Organisation | undefined;
profile: {
displayName: rawInfo.User?.name ?? "",
givenName: rawInfo.User?.given_name ?? "",
familyName: rawInfo.User?.family_name ?? "",
},
idpLinks: [idpLink],
};
return req;
},
[ProviderSlug.AZURE]: (idp: IDPInformation) => {
const rawInfo = idp.rawInformation?.toJson() as {
mail: string;
displayName?: string;
givenName?: string;
surname?: string;
};
const idpLink: PartialMessage<IDPLink> = {
idpId: idp.idpId,
userId: idp.userId,
userName: idp.userName,
};
const req: PartialMessage<AddHumanUserRequest> = {
username: idp.userName,
email: {
email: rawInfo?.mail,
verification: { case: "isVerified", value: true },
},
// organisation: Organisation | undefined;
profile: {
displayName: rawInfo?.displayName ?? "",
givenName: rawInfo?.givenName ?? "",
familyName: rawInfo?.surname ?? "",
},
idpLinks: [idpLink],
};
return req;
},
[ProviderSlug.GITHUB]: (idp: IDPInformation) => {
const rawInfo = idp.rawInformation?.toJson() as {
email: string;
name: string;
};
const idpLink: PartialMessage<IDPLink> = {
idpId: idp.idpId,
userId: idp.userId,
userName: idp.userName,
};
const req: PartialMessage<AddHumanUserRequest> = {
username: idp.userName,
email: {
email: rawInfo?.email,
verification: { case: "isVerified", value: true },
},
// organisation: Organisation | undefined;
profile: {
displayName: rawInfo?.name ?? "",
givenName: rawInfo?.name ?? "",
familyName: rawInfo?.name ?? "",
},
idpLinks: [idpLink],
};
return req;
},
};
function retrieveIDPIntent(id: string, token: string) {
return userService.retrieveIdentityProviderIntent(
{ idpIntentId: id, idpIntentToken: token },
{},
);
}
function createUser(
provider: ProviderSlug,
info: IDPInformation,
): Promise<string> {
const userData = PROVIDER_MAPPING[provider](info);
return userService.addHumanUser(userData, {}).then((resp) => resp.userId);
}
export default async function Page({ export default async function Page({
searchParams, searchParams,
params, params,
}: { }: {
searchParams: Record<string | number | symbol, string | undefined>; searchParams: Record<string | number | symbol, string | undefined>;
params: { provider: ProviderSlug }; params: { provider: string };
}) { }) {
const { id, token, authRequestId, organization } = searchParams; const { id, token, authRequestId, organization } = searchParams;
const { provider } = params; const { provider } = params;
const branding = await getBrandingSettings(organization); const branding = await getBrandingSettings(organization);
if (provider && id && token) { if (provider && id && token) {
return retrieveIDPIntent(id, token) return retrieveIDPIntent(id, token)
.then((resp) => { .then(async (resp) => {
const { idpInformation, userId } = resp; const { idpInformation, userId } = resp;
if (idpInformation) {
// handle login
if (userId) { if (userId) {
// TODO: update user if idp.options.isAutoUpdate is true
return ( return (
<DynamicTheme branding={branding}> <DynamicTheme branding={branding}>
<div className="flex flex-col items-center space-y-4"> <div className="flex flex-col items-center space-y-4">
@@ -151,24 +45,52 @@ export default async function Page({
</div> </div>
</DynamicTheme> </DynamicTheme>
); );
}
if (idpInformation) {
const idp = await getIDPByID(idpInformation.idpId);
const options = idp?.config?.options;
// search for potential user via username, then link
if (options?.isLinkingAllowed) {
let foundUser;
const email =
PROVIDER_MAPPING[provider](idpInformation).email?.email;
if (options.autoLinking === AutoLinkingOption.EMAIL && email) {
foundUser = await listUsers({ email }).then((response) => {
return response.result ? response.result[0] : null;
});
} else if (options.autoLinking === AutoLinkingOption.USERNAME) {
foundUser = await listUsers(
options.autoLinking === AutoLinkingOption.USERNAME
? { userName: idpInformation.userName }
: { email },
).then((response) => {
return response.result ? response.result[0] : null;
});
} else { } else {
// handle register foundUser = await listUsers({
return createUser(provider, idpInformation) userName: idpInformation.userName,
.then((userId) => { email,
}).then((response) => {
return response.result ? response.result[0] : null;
});
}
if (foundUser) {
const idpLink = await addIDPLink(
{
id: idpInformation.idpId,
userId: idpInformation.userId,
userName: idpInformation.userName,
},
foundUser.userId,
).catch((error) => {
return ( return (
<DynamicTheme branding={branding}> <DynamicTheme branding={branding}>
<div className="flex flex-col items-center space-y-4"> <div className="flex flex-col items-center space-y-4">
<h1>Register successful</h1> <h1>Linking failed</h1>
<div>You have successfully been registered!</div>
</div>
</DynamicTheme>
);
})
.catch((error) => {
return (
<DynamicTheme branding={branding}>
<div className="flex flex-col items-center space-y-4">
<h1>Register failed</h1>
<div className="w-full"> <div className="w-full">
{ {
<Alert type={AlertType.ALERT}> <Alert type={AlertType.ALERT}>
@@ -180,9 +102,66 @@ export default async function Page({
</DynamicTheme> </DynamicTheme>
); );
}); });
if (idpLink) {
return (
// TODO: possibily login user now
<DynamicTheme branding={branding}>
<div className="flex flex-col items-center space-y-4">
<h1>Account successfully linked</h1>
<div>Your account has successfully been linked!</div>
</div>
</DynamicTheme>
);
} }
}
}
if (options?.isCreationAllowed && options.isAutoCreation) {
const newUser = await createUser(provider, idpInformation);
if (newUser) {
return (
<DynamicTheme branding={branding}>
<div className="flex flex-col items-center space-y-4">
<h1>Register successful</h1>
<div>You have successfully been registered!</div>
</div>
</DynamicTheme>
);
}
}
// return login failed if no linking or creation is allowed and no user was found
return (
<DynamicTheme branding={branding}>
<div className="flex flex-col items-center space-y-4">
<h1>Login failed</h1>
<div className="w-full">
{
<Alert type={AlertType.ALERT}>
User could not be logged in
</Alert>
}
</div>
</div>
</DynamicTheme>
);
} else { } else {
throw new Error("Could not get user information."); return (
<DynamicTheme branding={branding}>
<div className="flex flex-col items-center space-y-4">
<h1>Login failed</h1>
<div className="w-full">
{
<Alert type={AlertType.ALERT}>
Could not get user information
</Alert>
}
</div>
</div>
</DynamicTheme>
);
} }
}) })
.catch((error) => { .catch((error) => {

View File

@@ -1,4 +1,4 @@
import { ProviderSlug } from "@/lib/demos"; import { idpTypeToSlug } from "@/lib/idp";
import { import {
getActiveIdentityProviders, getActiveIdentityProviders,
getLoginSettings, getLoginSettings,
@@ -7,14 +7,16 @@ import {
startIdentityProviderFlow, startIdentityProviderFlow,
} from "@/lib/zitadel"; } from "@/lib/zitadel";
import { createSessionForUserIdAndUpdateCookie } from "@/utils/session"; import { createSessionForUserIdAndUpdateCookie } from "@/utils/session";
import { IdentityProviderType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
import { NextRequest, NextResponse } from "next/server"; import { NextRequest, NextResponse } from "next/server";
export async function POST(request: NextRequest) { export async function POST(request: NextRequest) {
const body = await request.json(); const body = await request.json();
if (body) { if (body) {
const { loginName, authRequestId, organization } = body; const { loginName, authRequestId, organization } = body;
return listUsers(loginName, organization).then(async (users) => { return listUsers({
userName: loginName,
organizationId: organization,
}).then(async (users) => {
if (users.details?.totalResult == BigInt(1) && users.result[0].userId) { if (users.details?.totalResult == BigInt(1) && users.result[0].userId) {
const userId = users.result[0].userId; const userId = users.result[0].userId;
return createSessionForUserIdAndUpdateCookie( return createSessionForUserIdAndUpdateCookie(
@@ -64,28 +66,8 @@ export async function POST(request: NextRequest) {
const host = request.nextUrl.origin; const host = request.nextUrl.origin;
const identityProviderType = identityProviders[0].type; const identityProviderType = identityProviders[0].type;
let provider: string;
switch (identityProviderType) { const provider = idpTypeToSlug(identityProviderType);
case IdentityProviderType.GITHUB:
provider = "github";
break;
case IdentityProviderType.GOOGLE:
provider = "google";
break;
case IdentityProviderType.AZURE_AD:
provider = "azure";
break;
case IdentityProviderType.SAML:
provider = "saml";
break;
case IdentityProviderType.OIDC:
provider = "oidc";
break;
default:
provider = "oidc";
break;
}
const params = new URLSearchParams(); const params = new URLSearchParams();

View File

@@ -18,14 +18,19 @@ export async function POST(request: NextRequest) {
const session = await getSession(sessionCookie.id, sessionCookie.token); const session = await getSession(sessionCookie.id, sessionCookie.token);
const userId = session?.session?.factors?.user?.id; const userId = session?.session?.factors?.user?.id;
console.log("payload", {
if (userId) {
return verifyPasskeyRegistration(
passkeyId, passkeyId,
passkeyName, passkeyName,
publicKeyCredential, publicKeyCredential,
userId, userId,
) });
if (userId) {
return verifyPasskeyRegistration({
passkeyId,
passkeyName,
publicKeyCredential,
userId,
})
.then((resp) => { .then((resp) => {
return NextResponse.json(resp); return NextResponse.json(resp);
}) })

View File

@@ -5,7 +5,10 @@ export async function POST(request: NextRequest) {
const body = await request.json(); const body = await request.json();
if (body) { if (body) {
const { loginName, organization } = body; const { loginName, organization } = body;
return listUsers(loginName, organization).then((users) => { return listUsers({
userName: loginName,
organizationId: organization,
}).then((users) => {
if ( if (
users.details && users.details &&
Number(users.details.totalResult) == 1 && Number(users.details.totalResult) == 1 &&

View File

@@ -22,12 +22,15 @@ export async function POST(request: NextRequest) {
const userId = session?.session?.factors?.user?.id; const userId = session?.session?.factors?.user?.id;
if (userId) { if (userId) {
const req: PlainMessage<VerifyU2FRegistrationRequest> = { let req: PlainMessage<VerifyU2FRegistrationRequest> = {
publicKeyCredential, publicKeyCredential,
u2fId, u2fId,
userId, userId,
tokenName: passkeyName, tokenName: passkeyName,
}; };
req = VerifyU2FRegistrationRequest.fromJson(request as any);
return verifyU2FRegistration(req) return verifyU2FRegistration(req)
.then((resp) => { .then((resp) => {
return NextResponse.json(resp); return NextResponse.json(resp);

View File

@@ -18,6 +18,7 @@ import {
Prompt, Prompt,
} from "@zitadel/proto/zitadel/oidc/v2/authorization_pb"; } from "@zitadel/proto/zitadel/oidc/v2/authorization_pb";
import { IdentityProviderType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import { IdentityProviderType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
import { idpTypeToSlug } from "@/lib/idp";
async function loadSessions(ids: string[]): Promise<Session[]> { async function loadSessions(ids: string[]): Promise<Session[]> {
const response = await listSessions( const response = await listSessions(
@@ -168,28 +169,7 @@ export async function GET(request: NextRequest) {
const host = request.nextUrl.origin; const host = request.nextUrl.origin;
const identityProviderType = identityProviders[0].type; const identityProviderType = identityProviders[0].type;
let provider: string; let provider = idpTypeToSlug(identityProviderType);
switch (identityProviderType) {
case IdentityProviderType.GITHUB:
provider = "github";
break;
case IdentityProviderType.GOOGLE:
provider = "google";
break;
case IdentityProviderType.AZURE_AD:
provider = "azure";
break;
case IdentityProviderType.SAML:
provider = "saml";
break;
case IdentityProviderType.OIDC:
provider = "oidc";
break;
default:
provider = "oidc";
break;
}
const params = new URLSearchParams(); const params = new URLSearchParams();

View File

@@ -4,12 +4,6 @@ export type Item = {
description?: string; description?: string;
}; };
export enum ProviderSlug {
GOOGLE = "google",
GITHUB = "github",
AZURE = "microsoft",
}
export const demos: { name: string; items: Item[] }[] = [ export const demos: { name: string; items: Item[] }[] = [
{ {
name: "Login", name: "Login",

131
apps/login/src/lib/idp.ts Normal file
View File

@@ -0,0 +1,131 @@
import { AddHumanUserRequest } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
import { IDPInformation, IDPLink } from "@zitadel/proto/zitadel/user/v2/idp_pb";
import { IdentityProviderType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
import { PartialMessage } from "@zitadel/client";
// This maps the IdentityProviderType to a slug which is used in the /success and /failure routes
export function idpTypeToSlug(idpType: IdentityProviderType) {
switch (idpType) {
case IdentityProviderType.GITHUB:
return "github";
case IdentityProviderType.GOOGLE:
return "google";
case IdentityProviderType.AZURE_AD:
return "azure";
case IdentityProviderType.SAML:
return "saml";
case IdentityProviderType.OIDC:
return "oidc";
default:
throw new Error("Unknown identity provider type");
}
}
// this maps the IDPInformation to the AddHumanUserRequest which is used when creating a user or linking a user (email)
// TODO: extend this object from a other file which can be overwritten by customers like map = { ...PROVIDER_MAPPING, ...customerMap }
export type OIDC_USER = {
User: {
email: string;
name?: string;
given_name?: string;
family_name?: string;
};
};
export const PROVIDER_MAPPING: {
[provider: string]: (
rI: IDPInformation,
) => PartialMessage<AddHumanUserRequest>;
} = {
[idpTypeToSlug(IdentityProviderType.GOOGLE)]: (idp: IDPInformation) => {
const rawInfo = idp.rawInformation?.toJson() as OIDC_USER;
console.log(rawInfo);
const idpLink: PartialMessage<IDPLink> = {
idpId: idp.idpId,
userId: idp.userId,
userName: idp.userName,
};
const req: PartialMessage<AddHumanUserRequest> = {
username: idp.userName,
email: {
email: rawInfo.User?.email,
verification: { case: "isVerified", value: true },
},
profile: {
displayName: rawInfo.User?.name ?? "",
givenName: rawInfo.User?.given_name ?? "",
familyName: rawInfo.User?.family_name ?? "",
},
idpLinks: [idpLink],
};
return req;
},
[idpTypeToSlug(IdentityProviderType.AZURE_AD)]: (idp: IDPInformation) => {
const rawInfo = idp.rawInformation?.toJson() as {
jobTitle: string;
mail: string;
mobilePhone: string;
preferredLanguage: string;
id: string;
displayName?: string;
givenName?: string;
surname?: string;
officeLocation?: string;
userPrincipalName: string;
};
const idpLink: PartialMessage<IDPLink> = {
idpId: idp.idpId,
userId: idp.userId,
userName: idp.userName,
};
console.log(rawInfo, rawInfo.userPrincipalName);
const req: PartialMessage<AddHumanUserRequest> = {
username: idp.userName,
email: {
email: rawInfo.mail || rawInfo.userPrincipalName || "",
verification: { case: "isVerified", value: true },
},
profile: {
displayName: rawInfo.displayName ?? "",
givenName: rawInfo.givenName ?? "",
familyName: rawInfo.surname ?? "",
},
idpLinks: [idpLink],
};
return req;
},
[idpTypeToSlug(IdentityProviderType.GITHUB)]: (idp: IDPInformation) => {
const rawInfo = idp.rawInformation?.toJson() as {
email: string;
name: string;
};
const idpLink: PartialMessage<IDPLink> = {
idpId: idp.idpId,
userId: idp.userId,
userName: idp.userName,
};
const req: PartialMessage<AddHumanUserRequest> = {
username: idp.userName,
email: {
email: rawInfo.email,
verification: { case: "isVerified", value: true },
},
profile: {
displayName: rawInfo.name ?? "",
givenName: rawInfo.name ?? "",
familyName: rawInfo.name ?? "",
},
idpLinks: [idpLink],
};
return req;
},
};

View File

@@ -3,23 +3,27 @@ import {
createSessionServiceClient, createSessionServiceClient,
createSettingsServiceClient, createSettingsServiceClient,
createUserServiceClient, createUserServiceClient,
createIdpServiceClient,
makeReqCtx, makeReqCtx,
} from "@zitadel/client/v2"; } from "@zitadel/client/v2";
import { createManagementServiceClient } from "@zitadel/client/v1"; import { createManagementServiceClient } from "@zitadel/client/v1";
import { createServerTransport } from "@zitadel/node"; import { createServerTransport } from "@zitadel/node";
import { GetActiveIdentityProvidersRequest } from "@zitadel/proto/zitadel/settings/v2/settings_service_pb";
import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
import { RequestChallenges } from "@zitadel/proto/zitadel/session/v2/challenge_pb"; import { RequestChallenges } from "@zitadel/proto/zitadel/session/v2/challenge_pb";
import { import {
RetrieveIdentityProviderIntentRequest, RetrieveIdentityProviderIntentRequest,
VerifyPasskeyRegistrationRequest,
VerifyU2FRegistrationRequest, VerifyU2FRegistrationRequest,
} from "@zitadel/proto/zitadel/user/v2/user_service_pb"; } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
import { IDPInformation } from "@zitadel/proto/zitadel/user/v2/idp_pb";
import { CreateCallbackRequest } from "@zitadel/proto/zitadel/oidc/v2/oidc_service_pb"; import { CreateCallbackRequest } from "@zitadel/proto/zitadel/oidc/v2/oidc_service_pb";
import { TextQueryMethod } from "@zitadel/proto/zitadel/object/v2/object_pb"; import { TextQueryMethod } from "@zitadel/proto/zitadel/object/v2/object_pb";
import type { RedirectURLs } from "@zitadel/proto/zitadel/user/v2/idp_pb"; import type { RedirectURLs } from "@zitadel/proto/zitadel/user/v2/idp_pb";
import { ProviderSlug } from "./demos"; import { PartialMessage, PlainMessage } from "@zitadel/client";
import { PlainMessage } from "@zitadel/client"; import { SearchQuery as UserSearchQuery } from "@zitadel/proto/zitadel/user/v2/query_pb";
import { IdentityProviderType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
import { PROVIDER_MAPPING } from "./idp";
const SESSION_LIFETIME_S = 3000; const SESSION_LIFETIME_S = 3000;
@@ -35,6 +39,8 @@ export const sessionService = createSessionServiceClient(transport);
export const managementService = createManagementServiceClient(transport); export const managementService = createManagementServiceClient(transport);
export const userService = createUserServiceClient(transport); export const userService = createUserServiceClient(transport);
export const oidcService = createOIDCServiceClient(transport); export const oidcService = createOIDCServiceClient(transport);
export const idpService = createIdpServiceClient(transport);
export const settingsService = createSettingsServiceClient(transport); export const settingsService = createSettingsServiceClient(transport);
export async function getBrandingSettings(organization?: string) { export async function getBrandingSettings(organization?: string) {
@@ -233,12 +239,19 @@ export async function getUserByID(userId: string) {
return userService.getUserByID({ userId }, {}); return userService.getUserByID({ userId }, {});
} }
export async function listUsers(userName: string, organizationId: string) { export async function listUsers({
return userService.listUsers( userName,
{ email,
queries: organizationId organizationId,
? [ }: {
{ userName?: string;
email?: string;
organizationId?: string;
}) {
const queries: PartialMessage<UserSearchQuery>[] = [];
if (userName) {
queries.push({
query: { query: {
case: "userNameQuery", case: "userNameQuery",
value: { value: {
@@ -246,27 +259,34 @@ export async function listUsers(userName: string, organizationId: string) {
method: TextQueryMethod.EQUALS, method: TextQueryMethod.EQUALS,
}, },
}, },
}, });
{ }
if (organizationId) {
queries.push({
query: { query: {
case: "organizationIdQuery", case: "organizationIdQuery",
value: { value: {
organizationId, organizationId,
}, },
}, },
}, });
] }
: [
{ if (email) {
queries.push({
query: { query: {
case: "userNameQuery", case: "emailQuery",
value: { value: {
userName, emailAddress: email,
method: TextQueryMethod.EQUALS,
}, },
}, },
}, });
], }
return userService.listUsers(
{
queries: queries,
}, },
{}, {},
); );
@@ -276,14 +296,6 @@ export async function getOrgByDomain(domain: string) {
return managementService.getOrgByDomainGlobal({ domain }, {}); return managementService.getOrgByDomainGlobal({ domain }, {});
} }
export const PROVIDER_NAME_MAPPING: {
[provider: string]: string;
} = {
[ProviderSlug.GOOGLE]: "Google",
[ProviderSlug.GITHUB]: "GitHub",
[ProviderSlug.AZURE]: "Microft",
};
export async function startIdentityProviderFlow({ export async function startIdentityProviderFlow({
idpId, idpId,
urls, urls,
@@ -348,6 +360,44 @@ export async function resendEmailCode(userId: string) {
); );
} }
export function retrieveIDPIntent(id: string, token: string) {
return userService.retrieveIdentityProviderIntent(
{ idpIntentId: id, idpIntentToken: token },
{},
);
}
export function getIDPByID(id: string) {
return idpService.getIDPByID({ id }, {}).then((resp) => resp.idp);
}
export function addIDPLink(
idp: {
id: string;
userId: string;
userName: string;
},
userId: string,
) {
return userService.addIDPLink(
{
idpLink: {
userId: idp.userId,
idpId: idp.id,
userName: idp.userName,
},
userId,
},
{},
);
}
export function createUser(provider: string, info: IDPInformation) {
const userData = PROVIDER_MAPPING[provider](info);
console.log("ud", userData);
return userService.addHumanUser(userData, {});
}
/** /**
* *
* @param userId the id of the user where the email should be set * @param userId the id of the user where the email should be set
@@ -433,24 +483,11 @@ export async function getActiveIdentityProviders(orgId?: string) {
* @returns the newly set email * @returns the newly set email
*/ */
export async function verifyPasskeyRegistration( export async function verifyPasskeyRegistration(
passkeyId: string, request: PartialMessage<VerifyPasskeyRegistrationRequest>,
passkeyName: string,
publicKeyCredential:
| {
[key: string]: any;
}
| undefined,
userId: string,
) { ) {
return userService.verifyPasskeyRegistration( // TODO: find a better way to handle this
{ request = VerifyPasskeyRegistrationRequest.fromJson(request as any);
passkeyId, return userService.verifyPasskeyRegistration(request, {});
passkeyName,
publicKeyCredential,
userId,
},
{},
);
} }
/** /**

View File

@@ -18,11 +18,14 @@ export function middleware(request: NextRequest) {
requestHeaders.set("x-zitadel-login-client", SERVICE_USER_ID); requestHeaders.set("x-zitadel-login-client", SERVICE_USER_ID);
// this is a workaround for the next.js server not forwarding the host header // this is a workaround for the next.js server not forwarding the host header
requestHeaders.set("x-zitadel-forwarded", `host="${request.nextUrl.host}"`); // requestHeaders.set("x-zitadel-forwarded", `host="${request.nextUrl.host}"`);
// requestHeaders.set("x-zitadel-public-host", `${request.nextUrl.host}`); requestHeaders.set("x-zitadel-public-host", `${request.nextUrl.host}`);
// this is a workaround for the next.js server not forwarding the host header // this is a workaround for the next.js server not forwarding the host header
// requestHeaders.set("x-zitadel-instance-host", `${INSTANCE}`); requestHeaders.set(
"x-zitadel-instance-host",
`${INSTANCE}`.replace("https://", ""),
);
const responseHeaders = new Headers(); const responseHeaders = new Headers();
responseHeaders.set("Access-Control-Allow-Origin", "*"); responseHeaders.set("Access-Control-Allow-Origin", "*");

View File

@@ -8,6 +8,7 @@ import Alert from "./Alert";
import { Spinner } from "./Spinner"; import { Spinner } from "./Spinner";
import BackButton from "./BackButton"; import BackButton from "./BackButton";
import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
import { RequestChallenges } from "@zitadel/proto/zitadel/session/v2/challenge_pb";
// either loginName or sessionId must be provided // either loginName or sessionId must be provided
type Props = { type Props = {
@@ -78,7 +79,7 @@ export default function LoginPasskey({
loginName, loginName,
sessionId, sessionId,
organization, organization,
challenges: { challenges: RequestChallenges.fromJson({
webAuthN: { webAuthN: {
domain: "", domain: "",
// USER_VERIFICATION_REQUIREMENT_UNSPECIFIED = 0; // USER_VERIFICATION_REQUIREMENT_UNSPECIFIED = 0;
@@ -87,7 +88,7 @@ export default function LoginPasskey({
// USER_VERIFICATION_REQUIREMENT_DISCOURAGED = 3; - mfa // USER_VERIFICATION_REQUIREMENT_DISCOURAGED = 3; - mfa
userVerificationRequirement: userVerificationRequirement, userVerificationRequirement: userVerificationRequirement,
}, },
}, }),
authRequestId, authRequestId,
}), }),
}); });

View File

@@ -92,7 +92,7 @@ export default function RegisterPasskey({
return submitRegister().then((resp: RegisterPasskeyResponse) => { return submitRegister().then((resp: RegisterPasskeyResponse) => {
const passkeyId = resp.passkeyId; const passkeyId = resp.passkeyId;
const options: CredentialCreationOptions = const options: CredentialCreationOptions =
(resp.publicKeyCredentialCreationOptions?.toJson() as CredentialCreationOptions) ?? (resp.publicKeyCredentialCreationOptions as CredentialCreationOptions) ??
{}; {};
if (options?.publicKey) { if (options?.publicKey) {
@@ -143,6 +143,7 @@ export default function RegisterPasskey({
), ),
}, },
}; };
return submitVerify(passkeyId, "", data, sessionId).then(() => { return submitVerify(passkeyId, "", data, sessionId).then(() => {
const params = new URLSearchParams(); const params = new URLSearchParams();
@@ -194,10 +195,11 @@ export default function RegisterPasskey({
type="button" type="button"
variant={ButtonVariants.Secondary} variant={ButtonVariants.Secondary}
onClick={() => { onClick={() => {
const params = new URLSearchParams();
if (authRequestId) { if (authRequestId) {
params.set("authRequest", authRequestId); const params = new URLSearchParams({
} authRequest: authRequestId,
});
if (sessionId) { if (sessionId) {
params.set("sessionId", sessionId); params.set("sessionId", sessionId);
} }
@@ -207,6 +209,18 @@ export default function RegisterPasskey({
} }
router.push("/login?" + params); router.push("/login?" + params);
} else {
const params = new URLSearchParams();
if (sessionId) {
params.append("sessionId", sessionId);
}
if (organization) {
params.append("organization", organization);
}
router.push("/signedin?" + params);
}
}} }}
> >
skip skip

View File

@@ -90,7 +90,7 @@ export default function RegisterU2F({
return submitRegister().then((resp: RegisterU2FResponse) => { return submitRegister().then((resp: RegisterU2FResponse) => {
const u2fId = resp.u2fId; const u2fId = resp.u2fId;
const options: CredentialCreationOptions = const options: CredentialCreationOptions =
(resp.publicKeyCredentialCreationOptions?.toJson() as CredentialCreationOptions) ?? (resp.publicKeyCredentialCreationOptions as CredentialCreationOptions) ??
{}; {};
if (options.publicKey) { if (options.publicKey) {

View File

@@ -121,11 +121,12 @@ export default function SessionItem({
<XCircleIcon <XCircleIcon
className="hidden group-hover:block h-5 w-5 transition-all opacity-50 hover:opacity-100" className="hidden group-hover:block h-5 w-5 transition-all opacity-50 hover:opacity-100"
onClick={() => onClick={(event) => {
event.preventDefault();
clearSession(session.id).then(() => { clearSession(session.id).then(() => {
reload(); reload();
}) });
} }}
/> />
</div> </div>
</Link> </Link>

View File

@@ -1,6 +1,6 @@
"use client"; "use client";
import { ReactNode, useState } from "react";
import { ReactNode, useState } from "react";
import { import {
SignInWithGitlab, SignInWithGitlab,
SignInWithAzureAD, SignInWithAzureAD,
@@ -8,10 +8,10 @@ import {
SignInWithGithub, SignInWithGithub,
} from "@zitadel/react"; } from "@zitadel/react";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { ProviderSlug } from "@/lib/demos";
import Alert from "./Alert"; import Alert from "./Alert";
import BackButton from "./BackButton";
import { IdentityProvider } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import { IdentityProvider } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
import { idpTypeToSlug } from "@/lib/idp";
import { IdentityProviderType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
export interface SignInWithIDPProps { export interface SignInWithIDPProps {
children?: ReactNode; children?: ReactNode;
@@ -36,7 +36,7 @@ export function SignInWithIDP({
const [error, setError] = useState<string>(""); const [error, setError] = useState<string>("");
const router = useRouter(); const router = useRouter();
async function startFlow(idpId: string, provider: ProviderSlug) { async function startFlow(idpId: string, provider: string) {
setLoading(true); setLoading(true);
const params = new URLSearchParams(); const params = new URLSearchParams();
@@ -78,62 +78,65 @@ export function SignInWithIDP({
{identityProviders && {identityProviders &&
identityProviders.map((idp, i) => { identityProviders.map((idp, i) => {
switch (idp.type) { switch (idp.type) {
case 6: // IdentityProviderType.IDENTITY_PROVIDER_TYPE_GITHUB: case IdentityProviderType.GITHUB:
return ( return (
<SignInWithGithub <SignInWithGithub
key={`idp-${i}`} key={`idp-${i}`}
onClick={() => onClick={() =>
startFlow(idp.id, ProviderSlug.GITHUB).then( startFlow(
({ authUrl }) => { idp.id,
idpTypeToSlug(IdentityProviderType.GITHUB),
).then(({ authUrl }) => {
router.push(authUrl); router.push(authUrl);
}, })
)
} }
></SignInWithGithub> ></SignInWithGithub>
); );
case 7: // IdentityProviderType.IDENTITY_PROVIDER_TYPE_GITHUB_ES: case IdentityProviderType.GITHUB_ES:
return ( return (
<SignInWithGithub <SignInWithGithub
key={`idp-${i}`} key={`idp-${i}`}
onClick={() => alert("TODO: unimplemented")} onClick={() => alert("TODO: unimplemented")}
></SignInWithGithub> ></SignInWithGithub>
); );
case 5: // IdentityProviderType.IDENTITY_PROVIDER_TYPE_AZURE_AD: case IdentityProviderType.AZURE_AD:
return ( return (
<SignInWithAzureAD <SignInWithAzureAD
key={`idp-${i}`} key={`idp-${i}`}
onClick={() => onClick={() =>
startFlow(idp.id, ProviderSlug.AZURE).then( startFlow(
({ authUrl }) => { idp.id,
idpTypeToSlug(IdentityProviderType.AZURE_AD),
).then(({ authUrl }) => {
router.push(authUrl); router.push(authUrl);
}, })
)
} }
></SignInWithAzureAD> ></SignInWithAzureAD>
); );
case 10: // IdentityProviderType.IDENTITY_PROVIDER_TYPE_GOOGLE: case IdentityProviderType.GOOGLE:
return ( return (
<SignInWithGoogle <SignInWithGoogle
key={`idp-${i}`} key={`idp-${i}`}
e2e="google" e2e="google"
name={idp.name} name={idp.name}
onClick={() => onClick={() =>
startFlow(idp.id, ProviderSlug.GOOGLE).then( startFlow(
({ authUrl }) => { idp.id,
idpTypeToSlug(IdentityProviderType.GOOGLE),
).then(({ authUrl }) => {
router.push(authUrl); router.push(authUrl);
}, })
)
} }
></SignInWithGoogle> ></SignInWithGoogle>
); );
case 8: // IdentityProviderType.IDENTITY_PROVIDER_TYPE_GITLAB: case IdentityProviderType.GITLAB:
return ( return (
<SignInWithGitlab <SignInWithGitlab
key={`idp-${i}`} key={`idp-${i}`}
onClick={() => alert("TODO: unimplemented")} onClick={() => alert("TODO: unimplemented")}
></SignInWithGitlab> ></SignInWithGitlab>
); );
case 9: //IdentityProviderType.IDENTITY_PROVIDER_TYPE_GITLAB_SELF_HOSTED: case IdentityProviderType.GITLAB_SELF_HOSTED:
return ( return (
<SignInWithGitlab <SignInWithGitlab
key={`idp-${i}`} key={`idp-${i}`}

View File

@@ -42,7 +42,9 @@ export default function UserAvatar({
loginName={loginName ?? ""} loginName={loginName ?? ""}
/> />
</div> </div>
<span className="ml-4 text-14px">{loginName}</span> <span className="ml-4 text-14px max-w-[250px] text-ellipsis overflow-hidden">
{loginName}
</span>
<span className="flex-grow"></span> <span className="flex-grow"></span>
{showDropdown && ( {showDropdown && (
<Link <Link

View File

@@ -38,7 +38,6 @@ export async function createSessionAndUpdateCookie(
? { ? {
user: { search: { case: "loginName", value: loginName } }, user: { search: { case: "loginName", value: loginName } },
password: { password }, password: { password },
// totp: { code: totpCode },
} }
: { user: { search: { case: "loginName", value: loginName } } }, : { user: { search: { case: "loginName", value: loginName } } },
challenges, challenges,
@@ -50,7 +49,7 @@ export async function createSessionAndUpdateCookie(
createdSession.sessionToken, createdSession.sessionToken,
).then((response) => { ).then((response) => {
if (response?.session && response.session?.factors?.user?.loginName) { if (response?.session && response.session?.factors?.user?.loginName) {
const sessionCookie: any = { const sessionCookie: CustomCookieData = {
id: createdSession.sessionId, id: createdSession.sessionId,
token: createdSession.sessionToken, token: createdSession.sessionToken,
creationDate: `${response.session.creationDate?.toDate().getTime() ?? ""}`, creationDate: `${response.session.creationDate?.toDate().getTime() ?? ""}`,
@@ -103,7 +102,7 @@ export async function createSessionForUserIdAndUpdateCookie(
createdSession.sessionToken, createdSession.sessionToken,
).then((response) => { ).then((response) => {
if (response?.session && response.session?.factors?.user?.loginName) { if (response?.session && response.session?.factors?.user?.loginName) {
const sessionCookie: any = { const sessionCookie: CustomCookieData = {
id: createdSession.sessionId, id: createdSession.sessionId,
token: createdSession.sessionToken, token: createdSession.sessionToken,
creationDate: `${response.session.creationDate?.toDate().getTime() ?? ""}`, creationDate: `${response.session.creationDate?.toDate().getTime() ?? ""}`,
@@ -153,7 +152,7 @@ export async function createSessionForIdpAndUpdateCookie(
createdSession.sessionToken, createdSession.sessionToken,
).then((response) => { ).then((response) => {
if (response?.session && response.session?.factors?.user?.loginName) { if (response?.session && response.session?.factors?.user?.loginName) {
const sessionCookie: any = { const sessionCookie: CustomCookieData = {
id: createdSession.sessionId, id: createdSession.sessionId,
token: createdSession.sessionToken, token: createdSession.sessionToken,
creationDate: `${response.session.creationDate?.toDate().getTime() ?? ""}`, creationDate: `${response.session.creationDate?.toDate().getTime() ?? ""}`,

View File

@@ -1,10 +1,12 @@
import { FeatureService } from "@zitadel/proto/zitadel/feature/v2/feature_service_connect"; import { FeatureService } from "@zitadel/proto/zitadel/feature/v2/feature_service_connect";
import { IdentityProviderService } from "@zitadel/proto/zitadel/idp/v2/idp_service_connect";
import { RequestContext } from "@zitadel/proto/zitadel/object/v2/object_pb"; import { RequestContext } from "@zitadel/proto/zitadel/object/v2/object_pb";
import { OIDCService } from "@zitadel/proto/zitadel/oidc/v2/oidc_service_connect"; import { OIDCService } from "@zitadel/proto/zitadel/oidc/v2/oidc_service_connect";
import { OrganizationService } from "@zitadel/proto/zitadel/org/v2/org_service_connect"; import { OrganizationService } from "@zitadel/proto/zitadel/org/v2/org_service_connect";
import { SessionService } from "@zitadel/proto/zitadel/session/v2/session_service_connect"; import { SessionService } from "@zitadel/proto/zitadel/session/v2/session_service_connect";
import { SettingsService } from "@zitadel/proto/zitadel/settings/v2/settings_service_connect"; import { SettingsService } from "@zitadel/proto/zitadel/settings/v2/settings_service_connect";
import { UserService } from "@zitadel/proto/zitadel/user/v2/user_service_connect"; import { UserService } from "@zitadel/proto/zitadel/user/v2/user_service_connect";
import { createClientFor } from "./helpers"; import { createClientFor } from "./helpers";
export const createUserServiceClient = createClientFor(UserService); export const createUserServiceClient = createClientFor(UserService);
@@ -13,6 +15,7 @@ export const createSessionServiceClient = createClientFor(SessionService);
export const createOIDCServiceClient = createClientFor(OIDCService); export const createOIDCServiceClient = createClientFor(OIDCService);
export const createOrganizationServiceClient = createClientFor(OrganizationService); export const createOrganizationServiceClient = createClientFor(OrganizationService);
export const createFeatureServiceClient = createClientFor(FeatureService); export const createFeatureServiceClient = createClientFor(FeatureService);
export const createIdpServiceClient = createClientFor(IdentityProviderService);
export function makeReqCtx(orgId: string | undefined): Partial<RequestContext> { export function makeReqCtx(orgId: string | undefined): Partial<RequestContext> {
return { return {

View File

@@ -2,39 +2,20 @@
import { ReactNode, forwardRef } from "react"; import { ReactNode, forwardRef } from "react";
import { SignInWithIdentityProviderProps } from "./SignInWith"; import { SignInWithIdentityProviderProps } from "./SignInWith";
import { IdpButtonClasses } from "./classes";
export const SignInWithAzureAD = forwardRef< export const SignInWithAzureAD = forwardRef<HTMLButtonElement, SignInWithIdentityProviderProps>(
HTMLButtonElement,
SignInWithIdentityProviderProps
>(
({ children, className = "", name = "", ...props }, ref): ReactNode => ( ({ children, className = "", name = "", ...props }, ref): ReactNode => (
<button <button type="button" ref={ref} className={`${IdpButtonClasses} ${className}`} {...props}>
type="button"
ref={ref}
className={`ztdl-w-full ztdl-cursor-pointer ztdl-flex ztdl-flex-row ztdl-items-center ztdl-bg-white ztdl-text-black dark:ztdl-bg-transparent dark:ztdl-text-white border ztdl-border-divider-light dark:ztdl-border-divider-dark rounded-md px-4 text-sm ${className}`}
{...props}
>
<div className="ztdl-h-12 ztdl-p-[10px] ztdl-w-12 ztdl-flex ztdl-items-center ztdl-justify-center"> <div className="ztdl-h-12 ztdl-p-[10px] ztdl-w-12 ztdl-flex ztdl-items-center ztdl-justify-center">
<svg <svg xmlns="http://www.w3.org/2000/svg" width="21" height="21" viewBox="0 0 21 21" className="w-full h-full">
xmlns="http://www.w3.org/2000/svg"
width="21"
height="21"
viewBox="0 0 21 21"
className="w-full h-full"
>
<path fill="#f25022" d="M1 1H10V10H1z"></path> <path fill="#f25022" d="M1 1H10V10H1z"></path>
<path fill="#00a4ef" d="M1 11H10V20H1z"></path> <path fill="#00a4ef" d="M1 11H10V20H1z"></path>
<path fill="#7fba00" d="M11 1H20V10H11z"></path> <path fill="#7fba00" d="M11 1H20V10H11z"></path>
<path fill="#ffb900" d="M11 11H20V20H11z"></path> <path fill="#ffb900" d="M11 11H20V20H11z"></path>
</svg> </svg>
</div> </div>
{children ? ( {children ? children : <span className="ztdl-ml-4">{name ? name : "Sign in with AzureAD"}</span>}
children
) : (
<span className="ztdl-ml-4">
{name ? name : "Sign in with AzureAD"}
</span>
)}
</button> </button>
), ),
); );

View File

@@ -2,18 +2,11 @@
import { ReactNode, forwardRef } from "react"; import { ReactNode, forwardRef } from "react";
import { SignInWithIdentityProviderProps } from "./SignInWith"; import { SignInWithIdentityProviderProps } from "./SignInWith";
import { IdpButtonClasses } from "./classes";
export const SignInWithGithub = forwardRef< export const SignInWithGithub = forwardRef<HTMLButtonElement, SignInWithIdentityProviderProps>(
HTMLButtonElement,
SignInWithIdentityProviderProps
>(
({ children, className = "", name = "", ...props }, ref): ReactNode => ( ({ children, className = "", name = "", ...props }, ref): ReactNode => (
<button <button type="button" ref={ref} className={`${IdpButtonClasses} ${className}`} {...props}>
type="button"
ref={ref}
className={`ztdl-h-[50px] ztdl-w-full ztdl-cursor-pointer ztdl-flex ztdl-flex-row ztdl-items-center ztdl-bg-white ztdl-text-black dark:ztdl-bg-transparent dark:ztdl-text-white border ztdl-border-divider-light dark:ztdl-border-divider-dark rounded-md px-4 text-sm ${className}`}
{...props}
>
<div className="ztdl-h-8 ztdl-w-8 ztdl-mx-2 flex items-center justify-center"> <div className="ztdl-h-8 ztdl-w-8 ztdl-mx-2 flex items-center justify-center">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@@ -46,11 +39,7 @@ export const SignInWithGithub = forwardRef<
></path> ></path>
</svg> </svg>
</div> </div>
{children ? ( {children ? children : <span className="ztdl-ml-4">{name ? name : "Sign in with GitHub"}</span>}
children
) : (
<span className="ztdl-ml-4">{name ? name : "Sign in with GitHub"}</span>
)}
</button> </button>
), ),
); );

View File

@@ -1,24 +1,12 @@
import { ReactNode, forwardRef } from "react"; import { ReactNode, forwardRef } from "react";
import { SignInWithIdentityProviderProps } from "./SignInWith"; import { SignInWithIdentityProviderProps } from "./SignInWith";
import { IdpButtonClasses } from "./classes";
export const SignInWithGitlab = forwardRef< export const SignInWithGitlab = forwardRef<HTMLButtonElement, SignInWithIdentityProviderProps>(
HTMLButtonElement,
SignInWithIdentityProviderProps
>(
({ children, className = "", name = "", ...props }, ref): ReactNode => ( ({ children, className = "", name = "", ...props }, ref): ReactNode => (
<button <button type="button" ref={ref} className={`${IdpButtonClasses} ${className}`} {...props}>
type="button"
ref={ref}
className={`ztdl-w-full ztdl-cursor-pointer ztdl-flex ztdl-flex-row ztdl-items-center ztdl-bg-white ztdl-text-black dark:ztdl-bg-transparent dark:ztdl-text-white border ztdl-border-divider-light dark:ztdl-border-divider-dark rounded-md px-4 text-sm ${className}`}
{...props}
>
<div className="ztdl-h-12 ztdl-w-12 flex items-center justify-center"> <div className="ztdl-h-12 ztdl-w-12 flex items-center justify-center">
<svg <svg xmlns="http://www.w3.org/2000/svg" width={25} height={24} fill="none">
xmlns="http://www.w3.org/2000/svg"
width={25}
height={24}
fill="none"
>
<path <path
fill="#e24329" fill="#e24329"
d="m24.507 9.5-.034-.09L21.082.562a.896.896 0 0 0-1.694.091l-2.29 7.01H7.825L5.535.653a.898.898 0 0 0-1.694-.09L.451 9.411.416 9.5a6.297 6.297 0 0 0 2.09 7.278l.012.01.03.022 5.16 3.867 2.56 1.935 1.554 1.176a1.051 1.051 0 0 0 1.268 0l1.555-1.176 2.56-1.935 5.197-3.89.014-.01A6.297 6.297 0 0 0 24.507 9.5z" d="m24.507 9.5-.034-.09L21.082.562a.896.896 0 0 0-1.694.091l-2.29 7.01H7.825L5.535.653a.898.898 0 0 0-1.694-.09L.451 9.411.416 9.5a6.297 6.297 0 0 0 2.09 7.278l.012.01.03.022 5.16 3.867 2.56 1.935 1.554 1.176a1.051 1.051 0 0 0 1.268 0l1.555-1.176 2.56-1.935 5.197-3.89.014-.01A6.297 6.297 0 0 0 24.507 9.5z"
@@ -37,11 +25,7 @@ export const SignInWithGitlab = forwardRef<
/> />
</svg> </svg>
</div> </div>
{children ? ( {children ? children : <span className="ztdl-ml-4">{name ? name : "Sign in with GitLab"}</span>}
children
) : (
<span className="ztdl-ml-4">{name ? name : "Sign in with GitLab"}</span>
)}
</button> </button>
), ),
); );

View File

@@ -1,24 +1,12 @@
import { ReactNode, forwardRef } from "react"; import { ReactNode, forwardRef } from "react";
import { SignInWithIdentityProviderProps } from "./SignInWith"; import { SignInWithIdentityProviderProps } from "./SignInWith";
import { IdpButtonClasses } from "./classes";
export const SignInWithGoogle = forwardRef< export const SignInWithGoogle = forwardRef<HTMLButtonElement, SignInWithIdentityProviderProps>(
HTMLButtonElement,
SignInWithIdentityProviderProps
>(
({ children, className = "", name = "", ...props }, ref): ReactNode => ( ({ children, className = "", name = "", ...props }, ref): ReactNode => (
<button <button type="button" ref={ref} className={`${IdpButtonClasses} ${className}`} {...props}>
type="button"
ref={ref}
className={`ztdl-w-full ztdl-cursor-pointer ztdl-flex ztdl-flex-row ztdl-items-center ztdl-bg-white ztdl-text-black dark:ztdl-bg-transparent dark:ztdl-text-white border ztdl-border-divider-light dark:ztdl-border-divider-dark rounded-md px-4 text-sm ${className}`}
{...props}
>
<div className="ztdl-h-12 ztdl-w-12 ztdl-flex ztdl-items-center ztdl-justify-center"> <div className="ztdl-h-12 ztdl-w-12 ztdl-flex ztdl-items-center ztdl-justify-center">
<svg <svg xmlns="http://www.w3.org/2000/svg" xmlSpace="preserve" id="Capa_1" viewBox="0 0 150 150">
xmlns="http://www.w3.org/2000/svg"
xmlSpace="preserve"
id="Capa_1"
viewBox="0 0 150 150"
>
<style> <style>
{ {
".st0{fill:#1a73e8}.st1{fill:#ea4335}.st2{fill:#4285f4}.st3{fill:#fbbc04}.st4{fill:#34a853}.st5{fill:#4caf50}.st6{fill:#1e88e5}.st7{fill:#e53935}.st8{fill:#c62828}.st9{fill:#fbc02d}.st10{fill:#1565c0}.st11{fill:#2e7d32}.st16{clip-path:url(#SVGID_2_)}.st17{fill:#188038}.st18,.st19{opacity:.2;fill:#fff;enable-background:new}.st19{opacity:.3;fill:#0d652d}.st20{clip-path:url(#SVGID_4_)}.st21{opacity:.3;fill:url(#_45_shadow_1_);enable-background:new}.st22{clip-path:url(#SVGID_6_)}.st23{fill:#fa7b17}.st24,.st25,.st26{opacity:.3;fill:#174ea6;enable-background:new}.st25,.st26{fill:#a50e0e}.st26{fill:#e37400}.st27{fill:url(#Finish_mask_1_)}.st28{fill:#fff}.st29{fill:#0c9d58}.st30,.st31{opacity:.2;fill:#004d40;enable-background:new}.st31{fill:#3e2723}.st32{fill:#ffc107}.st33{fill:#1a237e;enable-background:new}.st33,.st34{opacity:.2}.st35{fill:#1a237e}.st36{fill:url(#SVGID_7_)}.st37{fill:#fbbc05}.st38{clip-path:url(#SVGID_9_);fill:#e53935}.st39{clip-path:url(#SVGID_11_);fill:#fbc02d}.st40{clip-path:url(#SVGID_13_);fill:#e53935}.st41{clip-path:url(#SVGID_15_);fill:#fbc02d}" ".st0{fill:#1a73e8}.st1{fill:#ea4335}.st2{fill:#4285f4}.st3{fill:#fbbc04}.st4{fill:#34a853}.st5{fill:#4caf50}.st6{fill:#1e88e5}.st7{fill:#e53935}.st8{fill:#c62828}.st9{fill:#fbc02d}.st10{fill:#1565c0}.st11{fill:#2e7d32}.st16{clip-path:url(#SVGID_2_)}.st17{fill:#188038}.st18,.st19{opacity:.2;fill:#fff;enable-background:new}.st19{opacity:.3;fill:#0d652d}.st20{clip-path:url(#SVGID_4_)}.st21{opacity:.3;fill:url(#_45_shadow_1_);enable-background:new}.st22{clip-path:url(#SVGID_6_)}.st23{fill:#fa7b17}.st24,.st25,.st26{opacity:.3;fill:#174ea6;enable-background:new}.st25,.st26{fill:#a50e0e}.st26{fill:#e37400}.st27{fill:url(#Finish_mask_1_)}.st28{fill:#fff}.st29{fill:#0c9d58}.st30,.st31{opacity:.2;fill:#004d40;enable-background:new}.st31{fill:#3e2723}.st32{fill:#ffc107}.st33{fill:#1a237e;enable-background:new}.st33,.st34{opacity:.2}.st35{fill:#1a237e}.st36{fill:url(#SVGID_7_)}.st37{fill:#fbbc05}.st38{clip-path:url(#SVGID_9_);fill:#e53935}.st39{clip-path:url(#SVGID_11_);fill:#fbc02d}.st40{clip-path:url(#SVGID_13_);fill:#e53935}.st41{clip-path:url(#SVGID_15_);fill:#fbc02d}"
@@ -50,11 +38,7 @@ export const SignInWithGoogle = forwardRef<
/> />
</svg> </svg>
</div> </div>
{children ? ( {children ? children : <span className="ztdl-ml-4">{name ? name : "Sign in with Google"}</span>}
children
) : (
<span className="ztdl-ml-4">{name ? name : "Sign in with Google"}</span>
)}
</button> </button>
), ),
); );

View File

@@ -0,0 +1,2 @@
export const IdpButtonClasses =
"transition-all ztdl-w-full ztdl-cursor-pointer ztdl-flex ztdl-flex-row ztdl-items-center bg-background-light-400 text-text-light-500 dark:bg-background-dark-500 dark:text-text-dark-500 border border-divider-light hover:border-black dark:border-divider-dark hover:dark:border-white focus:border-primary-light-500 focus:dark:border-primary-dark-500 outline-none rounded-md px-4 text-sm";