generate user agent fingerprint id

This commit is contained in:
Max Peintner
2025-03-10 14:14:39 +01:00
parent 365a85eda4
commit a7e74efcf7
41 changed files with 121 additions and 200 deletions

View File

@@ -36,6 +36,7 @@
"*": "prettier --write --ignore-unknown"
},
"dependencies": {
"@fingerprintjs/fingerprintjs": "^4.6.1",
"@headlessui/react": "^2.1.9",
"@heroicons/react": "2.1.3",
"@tailwindcss/forms": "0.5.7",

View File

@@ -19,7 +19,6 @@ async function loadSessions({ serviceUrl }: { serviceUrl: string }) {
if (ids && ids.length) {
const response = await listSessions({
serviceUrl,
ids: ids.filter((id) => !!id) as string[],
});
return response?.sessions ?? [];
@@ -56,7 +55,6 @@ export default async function Page(props: {
const branding = await getBrandingSettings({
serviceUrl,
organization: organization ?? defaultOrganization,
});

View File

@@ -49,7 +49,6 @@ export default async function Page(props: {
return listAuthenticationMethodTypes({
serviceUrl,
userId,
}).then((methods) => {
return getUserByID({ serviceUrl, userId }).then((user) => {
@@ -74,7 +73,6 @@ export default async function Page(props: {
) {
return loadMostRecentSession({
serviceUrl,
sessionParams: {
loginName,
organization,
@@ -92,15 +90,10 @@ export default async function Page(props: {
const recent = await getSessionCookieById({ sessionId, organization });
return getSession({
serviceUrl,
sessionId: recent.id,
sessionToken: recent.token,
}).then((sessionResponse) => {
return getAuthMethodsAndUser(
serviceUrl,
sessionResponse.session,
);
return getAuthMethodsAndUser(serviceUrl, sessionResponse.session);
});
}
@@ -110,19 +103,16 @@ export default async function Page(props: {
const branding = await getBrandingSettings({
serviceUrl,
organization: sessionWithData.factors?.user?.organizationId,
});
const loginSettings = await getLoginSettings({
serviceUrl,
organization: sessionWithData.factors?.user?.organizationId,
});
const identityProviders = await getActiveIdentityProviders({
serviceUrl,
orgId: sessionWithData.factors?.user?.organizationId,
linking_allowed: true,
}).then((resp) => {

View File

@@ -29,13 +29,11 @@ export default async function Page(props: {
const branding = await getBrandingSettings({
serviceUrl,
organization,
});
const loginSettings = await getLoginSettings({
serviceUrl,
organization,
});
@@ -54,7 +52,6 @@ export default async function Page(props: {
if (userId) {
const userResponse = await getUserByID({
serviceUrl,
userId,
});
if (userResponse) {
@@ -70,7 +67,6 @@ export default async function Page(props: {
const authMethodsResponse = await listAuthenticationMethodTypes({
serviceUrl,
userId,
});
if (authMethodsResponse.authMethodTypes) {

View File

@@ -44,7 +44,6 @@ export default async function Page(props: {
const branding = await getBrandingSettings({
serviceUrl,
organization,
});
@@ -54,7 +53,6 @@ export default async function Page(props: {
const intent = await retrieveIDPIntent({
serviceUrl,
id,
token,
});
@@ -79,7 +77,6 @@ export default async function Page(props: {
const idp = await getIDPByID({
serviceUrl,
id: idpInformation.idpId,
});
const options = idp?.config?.options;
@@ -100,7 +97,6 @@ export default async function Page(props: {
try {
idpLink = await addIDPLink({
serviceUrl,
idp: {
id: idpInformation.idpId,
userId: idpInformation.userId,
@@ -145,7 +141,6 @@ export default async function Page(props: {
} else {
foundUser = await listUsers({
serviceUrl,
userName: idpInformation.userName,
email,
}).then((response) => {
@@ -158,7 +153,6 @@ export default async function Page(props: {
try {
idpLink = await addIDPLink({
serviceUrl,
idp: {
id: idpInformation.idpId,
userId: idpInformation.userId,
@@ -201,7 +195,6 @@ export default async function Page(props: {
// this just returns orgs where the suffix is set as primary domain
const orgs = await getOrgsByDomain({
serviceUrl,
domain: suffix,
});
const orgToCheckForDiscovery =
@@ -209,7 +202,6 @@ export default async function Page(props: {
const orgLoginSettings = await getLoginSettings({
serviceUrl,
organization: orgToCheckForDiscovery,
});
if (orgLoginSettings?.allowDomainDiscovery) {
@@ -230,7 +222,6 @@ export default async function Page(props: {
const newUser = await addHuman({
serviceUrl,
request: userData,
});

View File

@@ -20,7 +20,6 @@ export default async function Page(props: {
const identityProviders = await getActiveIdentityProviders({
serviceUrl,
orgId: organization,
}).then((resp) => {
return resp.identityProviders;
@@ -28,7 +27,6 @@ export default async function Page(props: {
const branding = await getBrandingSettings({
serviceUrl,
organization,
});

View File

@@ -34,19 +34,16 @@ export default async function Page(props: {
const loginSettings = await getLoginSettings({
serviceUrl,
organization,
});
const passwordComplexitySettings = await getPasswordComplexitySettings({
serviceUrl,
organization,
});
const branding = await getBrandingSettings({
serviceUrl,
organization,
});

View File

@@ -32,7 +32,6 @@ export default async function Page(props: {
const branding = await getBrandingSettings({
serviceUrl,
organization,
});
@@ -41,7 +40,6 @@ export default async function Page(props: {
if (userId) {
const userResponse = await getUserByID({
serviceUrl,
userId,
});
if (userResponse) {

View File

@@ -40,19 +40,16 @@ export default async function Page(props: {
const loginSettings = await getLoginSettings({
serviceUrl,
organization: organization ?? defaultOrganization,
});
const contextLoginSettings = await getLoginSettings({
serviceUrl,
organization,
});
const identityProviders = await getActiveIdentityProviders({
serviceUrl,
orgId: organization ?? defaultOrganization,
}).then((resp) => {
return resp.identityProviders;
@@ -60,7 +57,6 @@ export default async function Page(props: {
const branding = await getBrandingSettings({
serviceUrl,
organization: organization ?? defaultOrganization,
});

View File

@@ -38,7 +38,6 @@ export default async function Page(props: {
) {
return loadMostRecentSession({
serviceUrl,
sessionParams: {
loginName,
organization,
@@ -47,7 +46,6 @@ export default async function Page(props: {
if (session && session.factors?.user?.id) {
return listAuthenticationMethodTypes({
serviceUrl,
userId: session.factors.user.id,
}).then((methods) => {
return {
@@ -67,14 +65,12 @@ export default async function Page(props: {
const recent = await getSessionCookieById({ sessionId, organization });
return getSession({
serviceUrl,
sessionId: recent.id,
sessionToken: recent.token,
}).then((response) => {
if (response?.session && response.session.factors?.user?.id) {
return listAuthenticationMethodTypes({
serviceUrl,
userId: response.session.factors.user.id,
}).then((methods) => {
return {
@@ -88,7 +84,6 @@ export default async function Page(props: {
const branding = await getBrandingSettings({
serviceUrl,
organization,
});

View File

@@ -61,7 +61,6 @@ export default async function Page(props: {
return listAuthenticationMethodTypes({
serviceUrl,
userId,
}).then((methods) => {
return getUserByID({ serviceUrl, userId }).then((user) => {
@@ -85,7 +84,6 @@ export default async function Page(props: {
) {
return loadMostRecentSession({
serviceUrl,
sessionParams: {
loginName,
organization,
@@ -108,12 +106,10 @@ export default async function Page(props: {
const branding = await getBrandingSettings({
serviceUrl,
organization,
});
const loginSettings = await getLoginSettings({
serviceUrl,
organization: sessionWithData.factors?.user?.organizationId,
});

View File

@@ -47,7 +47,6 @@ export default async function Page(props: {
? await loadSessionById(serviceUrl, sessionId, organization)
: await loadMostRecentSession({
serviceUrl,
sessionParams: { loginName, organization },
});
@@ -59,7 +58,6 @@ export default async function Page(props: {
const recent = await getSessionCookieById({ sessionId, organization });
return getSession({
serviceUrl,
sessionId: recent.id,
sessionToken: recent.token,
}).then((response) => {
@@ -72,13 +70,11 @@ export default async function Page(props: {
// email links do not come with organization, thus we need to use the session's organization
const branding = await getBrandingSettings({
serviceUrl,
organization: organization ?? session?.factors?.user?.organizationId,
});
const loginSettings = await getLoginSettings({
serviceUrl,
organization: organization ?? session?.factors?.user?.organizationId,
});

View File

@@ -38,18 +38,15 @@ export default async function Page(props: {
const branding = await getBrandingSettings({
serviceUrl,
organization,
});
const loginSettings = await getLoginSettings({
serviceUrl,
organization,
});
const session = await loadMostRecentSession({
serviceUrl,
sessionParams: {
loginName,
organization,
@@ -61,7 +58,6 @@ export default async function Page(props: {
if (method === "time-based") {
await registerTOTP({
serviceUrl,
userId: session.factors.user.id,
})
.then((resp) => {
@@ -76,7 +72,6 @@ export default async function Page(props: {
// does not work
await addOTPSMS({
serviceUrl,
userId: session.factors.user.id,
}).catch((error) => {
error = new Error("Could not add OTP via SMS");
@@ -85,7 +80,6 @@ export default async function Page(props: {
// works
await addOTPEmail({
serviceUrl,
userId: session.factors.user.id,
}).catch((error) => {
error = new Error("Could not add OTP via Email");

View File

@@ -27,7 +27,6 @@ export default async function Page(props: {
? await loadSessionById(serviceUrl, sessionId, organization)
: await loadMostRecentSession({
serviceUrl,
sessionParams: { loginName, organization },
});
@@ -39,7 +38,6 @@ export default async function Page(props: {
const recent = await getSessionCookieById({ sessionId, organization });
return getSession({
serviceUrl,
sessionId: recent.id,
sessionToken: recent.token,
}).then((response) => {
@@ -51,7 +49,6 @@ export default async function Page(props: {
const branding = await getBrandingSettings({
serviceUrl,
organization,
});

View File

@@ -23,7 +23,6 @@ export default async function Page(props: {
const session = await loadMostRecentSession({
serviceUrl,
sessionParams: {
loginName,
organization,
@@ -32,7 +31,6 @@ export default async function Page(props: {
const branding = await getBrandingSettings({
serviceUrl,
organization,
});

View File

@@ -28,7 +28,6 @@ export default async function Page(props: {
// also allow no session to be found (ignoreUnkownUsername)
const sessionFactors = await loadMostRecentSession({
serviceUrl,
sessionParams: {
loginName,
organization,
@@ -37,19 +36,16 @@ export default async function Page(props: {
const branding = await getBrandingSettings({
serviceUrl,
organization,
});
const passwordComplexity = await getPasswordComplexitySettings({
serviceUrl,
organization: sessionFactors?.factors?.user?.organizationId,
});
const loginSettings = await getLoginSettings({
serviceUrl,
organization: sessionFactors?.factors?.user?.organizationId,
});

View File

@@ -43,7 +43,6 @@ export default async function Page(props: {
try {
sessionFactors = await loadMostRecentSession({
serviceUrl,
sessionParams: {
loginName,
organization,
@@ -56,12 +55,10 @@ export default async function Page(props: {
const branding = await getBrandingSettings({
serviceUrl,
organization: organization ?? defaultOrganization,
});
const loginSettings = await getLoginSettings({
serviceUrl,
organization: organization ?? defaultOrganization,
});

View File

@@ -34,7 +34,6 @@ export default async function Page(props: {
if (loginName) {
session = await loadMostRecentSession({
serviceUrl,
sessionParams: {
loginName,
organization,
@@ -44,19 +43,16 @@ export default async function Page(props: {
const branding = await getBrandingSettings({
serviceUrl,
organization,
});
const passwordComplexity = await getPasswordComplexitySettings({
serviceUrl,
organization: session?.factors?.user?.organizationId,
});
const loginSettings = await getLoginSettings({
serviceUrl,
organization,
});
@@ -65,7 +61,6 @@ export default async function Page(props: {
if (userId) {
const userResponse = await getUserByID({
serviceUrl,
userId,
});
user = userResponse.user;

View File

@@ -35,24 +35,20 @@ export default async function Page(props: {
const legal = await getLegalAndSupportSettings({
serviceUrl,
organization,
});
const passwordComplexitySettings = await getPasswordComplexitySettings({
serviceUrl,
organization,
});
const branding = await getBrandingSettings({
serviceUrl,
organization,
});
const loginSettings = await getLoginSettings({
serviceUrl,
organization,
});

View File

@@ -37,24 +37,20 @@ export default async function Page(props: {
const legal = await getLegalAndSupportSettings({
serviceUrl,
organization,
});
const passwordComplexitySettings = await getPasswordComplexitySettings({
serviceUrl,
organization,
});
const branding = await getBrandingSettings({
serviceUrl,
organization,
});
const loginSettings = await getLoginSettings({
serviceUrl,
organization,
});

View File

@@ -33,7 +33,6 @@ async function loadSession(
if (requestId && requestId.startsWith("oidc_")) {
return createCallback({
serviceUrl,
req: create(CreateCallbackRequestSchema, {
authRequestId: requestId,
callbackKind: {
@@ -67,7 +66,6 @@ async function loadSession(
return getSession({
serviceUrl,
sessionId: recent.id,
sessionToken: recent.token,
}).then((response) => {
@@ -86,16 +84,10 @@ export default async function Page(props: { searchParams: Promise<any> }) {
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
const { loginName, requestId, organization } = searchParams;
const sessionFactors = await loadSession(
serviceUrl,
loginName,
requestId,
);
const sessionFactors = await loadSession(serviceUrl, loginName, requestId);
const branding = await getBrandingSettings({
serviceUrl,
organization,
});
@@ -103,7 +95,6 @@ export default async function Page(props: { searchParams: Promise<any> }) {
if (!requestId) {
loginSettings = await getLoginSettings({
serviceUrl,
organization,
});
}

View File

@@ -29,7 +29,6 @@ export default async function Page(props: {
const branding = await getBrandingSettings({
serviceUrl,
organization,
});
@@ -37,7 +36,6 @@ export default async function Page(props: {
? await loadSessionById(serviceUrl, sessionId, organization)
: await loadMostRecentSession({
serviceUrl,
sessionParams: { loginName, organization },
});
@@ -49,7 +47,6 @@ export default async function Page(props: {
const recent = await getSessionCookieById({ sessionId, organization });
return getSession({
serviceUrl,
sessionId: recent.id,
sessionToken: recent.token,
}).then((response) => {

View File

@@ -23,7 +23,6 @@ export default async function Page(props: {
const sessionFactors = await loadMostRecentSession({
serviceUrl,
sessionParams: {
loginName,
organization,
@@ -32,7 +31,6 @@ export default async function Page(props: {
const branding = await getBrandingSettings({
serviceUrl,
organization,
});

View File

@@ -35,7 +35,6 @@ export default async function Page(props: { searchParams: Promise<any> }) {
const branding = await getBrandingSettings({
serviceUrl,
organization,
});
@@ -51,7 +50,6 @@ export default async function Page(props: { searchParams: Promise<any> }) {
if ("loginName" in searchParams) {
sessionFactors = await loadMostRecentSession({
serviceUrl,
sessionParams: {
loginName,
organization,
@@ -86,7 +84,6 @@ export default async function Page(props: { searchParams: Promise<any> }) {
const userResponse = await getUserByID({
serviceUrl,
userId,
});
if (userResponse) {

View File

@@ -159,7 +159,6 @@ export async function GET(request: NextRequest) {
if (orgDomain) {
const orgs = await getOrgsByDomain({
serviceUrl,
domain: orgDomain,
});
if (orgs.result && orgs.result.length === 1) {
@@ -367,7 +366,6 @@ export async function GET(request: NextRequest) {
try {
const { callbackUrl } = await createCallback({
serviceUrl,
req: create(CreateCallbackRequestSchema, {
authRequestId: requestId.replace("oidc_", ""),
callbackKind: {

View File

@@ -0,0 +1,63 @@
import FingerprintJS from "@fingerprintjs/fingerprintjs";
import { create } from "@zitadel/client";
import {
UserAgent,
UserAgentSchema,
} from "@zitadel/proto/zitadel/session/v2/session_pb";
import { cookies, headers } from "next/headers";
import { userAgent } from "next/server";
export async function getFingerprintId() {
const fp = await FingerprintJS.load();
const result = await fp.get();
return result.visitorId;
}
export async function setFingerprintIdCookie(fingerprintId: string) {
const cookiesList = await cookies();
return cookiesList.set({
name: "fingerprintId",
value: fingerprintId,
httpOnly: true,
path: "/",
maxAge: 31536000, // 1 year
});
}
export async function getFingerprintIdCookie() {
const cookiesList = await cookies();
return cookiesList.get("fingerprintId");
}
export async function getOrSetFingerprintId(): Promise<string> {
const cookie = await getFingerprintIdCookie();
if (cookie) {
return cookie.value;
}
const fingerprintId = await getFingerprintId();
await setFingerprintIdCookie(fingerprintId);
return fingerprintId;
}
export async function getUserAgent(): Promise<UserAgent> {
const _headers = await headers();
const fingerprintId = await getOrSetFingerprintId();
const { device } = userAgent({ headers: _headers });
const userAgentHeader = _headers.get("user-agent");
const userAgentHeaderValues = userAgentHeader?.split(",");
const userAgentData: UserAgent = create(UserAgentSchema, {
ip: _headers.get("x-forwarded-for") ?? _headers.get("remoteAddress") ?? "",
header: { "user-agent": { values: userAgentHeaderValues } },
description: `${device?.type ?? "unknown type"}, ${device?.vendor ?? "unknown vendor"} ${device?.model ?? "unknown model"}`,
fingerprintId: fingerprintId,
});
return userAgentData;
}

View File

@@ -32,7 +32,6 @@ export async function setMyPassword({
const { session } = await getSession({
serviceUrl,
sessionId: sessionCookie.id,
sessionToken: sessionCookie.token,
});

View File

@@ -46,24 +46,23 @@ const passwordAttemptsHandler = (error: ConnectError) => {
throw error;
};
export async function createSessionAndUpdateCookie(
checks: Checks,
requestId: string | undefined,
lifetime?: Duration,
): Promise<Session> {
export async function createSessionAndUpdateCookie(command: {
checks: Checks;
requestId: string | undefined;
lifetime?: Duration;
}): Promise<Session> {
const _headers = await headers();
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
const createdSession = await createSessionFromChecks({
serviceUrl,
checks,
lifetime,
checks: command.checks,
lifetime: command.lifetime,
});
if (createdSession) {
return getSession({
serviceUrl,
sessionId: createdSession.sessionId,
sessionToken: createdSession.sessionToken,
}).then((response) => {
@@ -83,8 +82,8 @@ export async function createSessionAndUpdateCookie(
loginName: response.session.factors.user.loginName ?? "",
};
if (requestId) {
sessionCookie.requestId = requestId;
if (command.requestId) {
sessionCookie.requestId = command.requestId;
}
if (response.session.factors.user.organizationId) {
@@ -118,7 +117,6 @@ export async function createSessionForIdpAndUpdateCookie(
const createdSession = await createSessionForUserIdAndIdpIntent({
serviceUrl,
userId,
idpIntent,
lifetime,
@@ -139,7 +137,6 @@ export async function createSessionForIdpAndUpdateCookie(
const { session } = await getSession({
serviceUrl,
sessionId: createdSession.sessionId,
sessionToken: createdSession.sessionToken,
});
@@ -191,7 +188,6 @@ export async function setSessionAndUpdateCookie(
return setSession({
serviceUrl,
sessionId: recentCookie.id,
sessionToken: recentCookie.token,
challenges,
@@ -219,7 +215,6 @@ export async function setSessionAndUpdateCookie(
return getSession({
serviceUrl,
sessionId: sessionCookie.id,
sessionToken: sessionCookie.token,
}).then((response) => {

View File

@@ -62,6 +62,7 @@ export async function createNewSessionFromIdpIntent(
command: CreateNewSessionCommand,
) {
const _headers = await headers();
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
const host = _headers.get("host");
@@ -75,7 +76,6 @@ export async function createNewSessionFromIdpIntent(
const userResponse = await getUserByID({
serviceUrl,
userId: command.userId,
});
@@ -85,7 +85,6 @@ export async function createNewSessionFromIdpIntent(
const loginSettings = await getLoginSettings({
serviceUrl,
organization: userResponse.user.details?.resourceOwner,
});

View File

@@ -31,7 +31,6 @@ export async function inviteUser(command: InviteUserCommand) {
const human = await addHumanUser({
serviceUrl,
email: command.email,
firstName: command.firstName,
lastName: command.lastName,

View File

@@ -43,7 +43,6 @@ export async function sendLoginname(command: SendLoginnameCommand) {
const loginSettingsByContext = await getLoginSettings({
serviceUrl,
organization: command.organization,
});
@@ -53,7 +52,6 @@ export async function sendLoginname(command: SendLoginnameCommand) {
let searchUsersRequest: SearchUsersCommand = {
serviceUrl,
searchValue: command.loginName,
organizationId: command.organization,
loginSettings: loginSettingsByContext,
@@ -75,7 +73,6 @@ export async function sendLoginname(command: SendLoginnameCommand) {
const redirectUserToSingleIDPIfAvailable = async () => {
const identityProviders = await getActiveIdentityProviders({
serviceUrl,
orgId: command.organization,
}).then((resp) => {
return resp.identityProviders;
@@ -128,7 +125,6 @@ export async function sendLoginname(command: SendLoginnameCommand) {
const redirectUserToIDP = async (userId: string) => {
const identityProviders = await listIDPLinks({
serviceUrl,
userId,
}).then((resp) => {
return resp.result;
@@ -147,7 +143,6 @@ export async function sendLoginname(command: SendLoginnameCommand) {
const idp = await getIDPByID({
serviceUrl,
id: identityProviderId,
});
@@ -199,7 +194,6 @@ export async function sendLoginname(command: SendLoginnameCommand) {
const userLoginSettings = await getLoginSettings({
serviceUrl,
organization: user.details?.resourceOwner,
});
@@ -241,10 +235,10 @@ export async function sendLoginname(command: SendLoginnameCommand) {
user: { search: { case: "userId", value: userId } },
});
const session = await createSessionAndUpdateCookie(
const session = await createSessionAndUpdateCookie({
checks,
command.requestId,
);
requestId: command.requestId,
});
if (!session.factors?.user?.id) {
return { error: "Could not create session for user" };
@@ -257,7 +251,6 @@ export async function sendLoginname(command: SendLoginnameCommand) {
const methods = await listAuthenticationMethodTypes({
serviceUrl,
userId: session.factors?.user?.id,
});
@@ -416,7 +409,6 @@ export async function sendLoginname(command: SendLoginnameCommand) {
// this just returns orgs where the suffix is set as primary domain
const orgs = await getOrgsByDomain({
serviceUrl,
domain: suffix,
});
const orgToCheckForDiscovery =
@@ -424,7 +416,6 @@ export async function sendLoginname(command: SendLoginnameCommand) {
const orgLoginSettings = await getLoginSettings({
serviceUrl,
organization: orgToCheckForDiscovery,
});
if (orgLoginSettings?.allowDomainDiscovery) {

View File

@@ -64,7 +64,6 @@ export async function setOTP(command: SetOTPCommand) {
const loginSettings = await getLoginSettings({
serviceUrl,
organization: command.organization,
});

View File

@@ -53,7 +53,6 @@ export async function registerPasskeyLink(
const sessionCookie = await getSessionCookieById({ sessionId });
const session = await getSession({
serviceUrl,
sessionId: sessionCookie.id,
sessionToken: sessionCookie.token,
});
@@ -74,7 +73,6 @@ export async function registerPasskeyLink(
// use session token to add the passkey
const registerLink = await createPasskeyRegistrationLink({
serviceUrl,
userId,
});
@@ -84,7 +82,6 @@ export async function registerPasskeyLink(
return registerPasskey({
serviceUrl,
userId,
code: registerLink.code,
domain: hostname,
@@ -112,7 +109,6 @@ export async function verifyPasskeyRegistration(command: VerifyPasskeyCommand) {
});
const session = await getSession({
serviceUrl,
sessionId: sessionCookie.id,
sessionToken: sessionCookie.token,
});
@@ -124,7 +120,6 @@ export async function verifyPasskeyRegistration(command: VerifyPasskeyCommand) {
return zitadelVerifyPasskeyRegistration({
serviceUrl,
request: create(VerifyPasskeyRegistrationRequestSchema, {
passkeyId: command.passkeyId,
publicKeyCredential: command.publicKeyCredential,
@@ -162,7 +157,6 @@ export async function sendPasskey(command: SendPasskeyCommand) {
const loginSettings = await getLoginSettings({
serviceUrl,
organization,
});
@@ -186,7 +180,6 @@ export async function sendPasskey(command: SendPasskeyCommand) {
const userResponse = await getUserByID({
serviceUrl,
userId: session?.factors?.user?.id,
});

View File

@@ -56,7 +56,6 @@ export async function resetPassword(command: ResetPasswordCommand) {
const users = await listUsers({
serviceUrl,
loginName: command.loginName,
organizationId: command.organization,
});
@@ -106,7 +105,6 @@ export async function sendPassword(command: UpdateSessionCommand) {
if (!sessionCookie) {
const users = await listUsers({
serviceUrl,
loginName: command.loginName,
organizationId: command.organization,
});
@@ -121,21 +119,19 @@ export async function sendPassword(command: UpdateSessionCommand) {
loginSettings = await getLoginSettings({
serviceUrl,
organization: command.organization,
});
try {
session = await createSessionAndUpdateCookie(
session = await createSessionAndUpdateCookie({
checks,
command.requestId,
loginSettings?.passwordCheckLifetime,
);
requestId: command.requestId,
lifetime: loginSettings?.passwordCheckLifetime,
});
} catch (error: any) {
if ("failedAttempts" in error && error.failedAttempts) {
const lockoutSettings = await getLockoutSettings({
serviceUrl,
orgId: command.organization,
});
@@ -167,7 +163,6 @@ export async function sendPassword(command: UpdateSessionCommand) {
if ("failedAttempts" in error && error.failedAttempts) {
const lockoutSettings = await getLockoutSettings({
serviceUrl,
orgId: command.organization,
});
@@ -189,7 +184,6 @@ export async function sendPassword(command: UpdateSessionCommand) {
const userResponse = await getUserByID({
serviceUrl,
userId: session?.factors?.user?.id,
});
@@ -203,7 +197,6 @@ export async function sendPassword(command: UpdateSessionCommand) {
if (!loginSettings) {
loginSettings = await getLoginSettings({
serviceUrl,
organization:
command.organization ?? session.factors?.user?.organizationId,
});
@@ -217,7 +210,6 @@ export async function sendPassword(command: UpdateSessionCommand) {
const expirySettings = await getPasswordExpirySettings({
serviceUrl,
orgId: command.organization ?? session.factors?.user?.organizationId,
});
@@ -256,7 +248,6 @@ export async function sendPassword(command: UpdateSessionCommand) {
if (command.checks && command.checks.password && session.factors?.user?.id) {
const response = await listAuthenticationMethodTypes({
serviceUrl,
userId: session.factors.user.id,
});
if (response.authMethodTypes && response.authMethodTypes.length) {
@@ -317,7 +308,6 @@ export async function changePassword(command: {
// check for init state
const { user } = await getUserByID({
serviceUrl,
userId: command.userId,
});
@@ -328,7 +318,6 @@ export async function changePassword(command: {
return setUserPassword({
serviceUrl,
userId,
password: command.password,
user,
@@ -352,7 +341,6 @@ export async function checkSessionAndSetPassword({
const { session } = await getSession({
serviceUrl,
sessionId: sessionCookie.id,
sessionToken: sessionCookie.token,
});
@@ -371,7 +359,6 @@ export async function checkSessionAndSetPassword({
// check if the user has no password set in order to set a password
const authmethods = await listAuthenticationMethodTypes({
serviceUrl,
userId: session.factors.user.id,
});
@@ -392,7 +379,6 @@ export async function checkSessionAndSetPassword({
const loginSettings = await getLoginSettings({
serviceUrl,
organization: session.factors.user.organizationId,
});

View File

@@ -38,7 +38,6 @@ export async function registerUser(command: RegisterUserCommand) {
const addResponse = await addHumanUser({
serviceUrl,
email: command.email,
firstName: command.firstName,
lastName: command.lastName,
@@ -52,7 +51,6 @@ export async function registerUser(command: RegisterUserCommand) {
const loginSettings = await getLoginSettings({
serviceUrl,
organization: command.organization,
});
@@ -69,11 +67,13 @@ export async function registerUser(command: RegisterUserCommand) {
const checks = create(ChecksSchema, checkPayload);
const session = await createSessionAndUpdateCookie(
const session = await createSessionAndUpdateCookie({
checks,
command.requestId,
command.password ? loginSettings?.passwordCheckLifetime : undefined,
);
requestId: command.requestId,
lifetime: command.password
? loginSettings?.passwordCheckLifetime
: undefined,
});
if (!session || !session.factors?.user) {
return { error: "Could not create session" };
@@ -93,7 +93,6 @@ export async function registerUser(command: RegisterUserCommand) {
} else {
const userResponse = await getUserByID({
serviceUrl,
userId: session?.factors?.user?.id,
});

View File

@@ -77,7 +77,6 @@ export async function continueWithSession({
const loginSettings = await getLoginSettings({
serviceUrl,
organization: session.factors?.user?.organizationId,
});
@@ -151,7 +150,6 @@ export async function updateSession(options: UpdateSessionCommand) {
const loginSettings = await getLoginSettings({
serviceUrl,
organization,
});
@@ -178,7 +176,6 @@ export async function updateSession(options: UpdateSessionCommand) {
if (checks && checks.password && session.factors?.user?.id) {
const response = await listAuthenticationMethodTypes({
serviceUrl,
userId: session.factors.user.id,
});
if (response.authMethodTypes && response.authMethodTypes.length) {
@@ -208,7 +205,6 @@ export async function clearSession(options: ClearSessionOptions) {
const deletedSession = await deleteSession({
serviceUrl,
sessionId: session.id,
sessionToken: session.token,
});
@@ -230,7 +226,6 @@ export async function cleanupSession({ sessionId }: CleanupSessionCommand) {
const deleteResponse = await deleteSession({
serviceUrl,
sessionId: sessionCookie.id,
sessionToken: sessionCookie.token,
});

View File

@@ -38,7 +38,6 @@ export async function addU2F(command: RegisterU2FCommand) {
const session = await getSession({
serviceUrl,
sessionId: sessionCookie.id,
sessionToken: sessionCookie.token,
});
@@ -83,7 +82,6 @@ export async function verifyU2F(command: VerifyU2FCommand) {
const session = await getSession({
serviceUrl,
sessionId: sessionCookie.id,
sessionToken: sessionCookie.token,
});

View File

@@ -34,7 +34,6 @@ export async function verifyTOTP(
return loadMostRecentSession({
serviceUrl,
sessionParams: {
loginName,
organization,
@@ -43,7 +42,6 @@ export async function verifyTOTP(
if (session?.factors?.user?.id) {
return verifyTOTPRegistration({
serviceUrl,
code,
userId: session.factors.user.id,
});
@@ -69,7 +67,6 @@ export async function sendVerification(command: VerifyUserByEmailCommand) {
const verifyResponse = command.isInvite
? await verifyInviteCode({
serviceUrl,
userId: command.userId,
verificationCode: command.code,
}).catch(() => {
@@ -77,7 +74,6 @@ export async function sendVerification(command: VerifyUserByEmailCommand) {
})
: await verifyEmail({
serviceUrl,
userId: command.userId,
verificationCode: command.code,
}).catch(() => {
@@ -109,7 +105,6 @@ export async function sendVerification(command: VerifyUserByEmailCommand) {
session = await getSession({
serviceUrl,
sessionId: sessionCookie.id,
sessionToken: sessionCookie.token,
}).then((response) => {
@@ -124,7 +119,6 @@ export async function sendVerification(command: VerifyUserByEmailCommand) {
const userResponse = await getUserByID({
serviceUrl,
userId: session?.factors?.user?.id,
});
@@ -136,7 +130,6 @@ export async function sendVerification(command: VerifyUserByEmailCommand) {
} else {
const userResponse = await getUserByID({
serviceUrl,
userId: command.userId,
});
@@ -155,7 +148,10 @@ export async function sendVerification(command: VerifyUserByEmailCommand) {
},
});
session = await createSessionAndUpdateCookie(checks, command.requestId);
session = await createSessionAndUpdateCookie({
checks,
requestId: command.requestId,
});
}
if (!session?.factors?.user?.id) {
@@ -172,13 +168,11 @@ export async function sendVerification(command: VerifyUserByEmailCommand) {
const loginSettings = await getLoginSettings({
serviceUrl,
organization: user.details?.resourceOwner,
});
const authMethodResponse = await listAuthenticationMethodTypes({
serviceUrl,
userId: user.userId,
});
@@ -320,7 +314,6 @@ export async function sendVerificationRedirectWithoutCheck(
session = await getSession({
serviceUrl,
sessionId: sessionCookie.id,
sessionToken: sessionCookie.token,
}).then((response) => {
@@ -335,7 +328,6 @@ export async function sendVerificationRedirectWithoutCheck(
const userResponse = await getUserByID({
serviceUrl,
userId: session?.factors?.user?.id,
});
@@ -347,7 +339,6 @@ export async function sendVerificationRedirectWithoutCheck(
} else if ("userId" in command) {
const userResponse = await getUserByID({
serviceUrl,
userId: command.userId,
});
@@ -366,7 +357,10 @@ export async function sendVerificationRedirectWithoutCheck(
},
});
session = await createSessionAndUpdateCookie(checks, command.requestId);
session = await createSessionAndUpdateCookie({
checks,
requestId: command.requestId,
});
}
if (!session?.factors?.user?.id) {
@@ -383,7 +377,6 @@ export async function sendVerificationRedirectWithoutCheck(
const authMethodResponse = await listAuthenticationMethodTypes({
serviceUrl,
userId: user.userId,
});

View File

@@ -22,7 +22,6 @@ type LoadMostRecentSessionParams = {
export async function loadMostRecentSession({
serviceUrl,
sessionParams,
}: LoadMostRecentSessionParams): Promise<Session | undefined> {
const recent = await getMostRecentCookieWithLoginname({
@@ -32,7 +31,6 @@ export async function loadMostRecentSession({
return getSession({
serviceUrl,
sessionId: recent.id,
sessionToken: recent.token,
}).then((resp: GetSessionResponse) => resp.session);

View File

@@ -46,6 +46,7 @@ import {
VerifyU2FRegistrationRequest,
} from "@zitadel/proto/zitadel/user/v2/user_service_pb";
import { unstable_cacheLife as cacheLife } from "next/cache";
import { getUserAgent } from "./fingerprint";
import { createServiceForHost } from "./service";
const useCache = process.env.DEBUG !== "true";
@@ -246,7 +247,9 @@ export async function createSessionFromChecks({
const sessionService: Client<typeof SessionService> =
await createServiceForHost(SessionService, serviceUrl);
return sessionService.createSession({ checks, lifetime }, {});
const userAgent = await getUserAgent();
return sessionService.createSession({ checks, lifetime, userAgent }, {});
}
export async function createSessionForUserIdAndIdpIntent({
@@ -266,6 +269,8 @@ export async function createSessionForUserIdAndIdpIntent({
const sessionService: Client<typeof SessionService> =
await createServiceForHost(SessionService, serviceUrl);
const userAgent = await getUserAgent();
return sessionService.createSession({
checks: {
user: {
@@ -277,6 +282,7 @@ export async function createSessionForUserIdAndIdpIntent({
idpIntent,
},
lifetime,
userAgent,
});
}
@@ -346,11 +352,7 @@ type ListSessionsCommand = {
ids: string[];
};
export async function listSessions({
serviceUrl,
ids,
}: ListSessionsCommand) {
export async function listSessions({ serviceUrl, ids }: ListSessionsCommand) {
const sessionService: Client<typeof SessionService> =
await createServiceForHost(SessionService, serviceUrl);

22
pnpm-lock.yaml generated
View File

@@ -74,6 +74,9 @@ importers:
apps/login:
dependencies:
'@fingerprintjs/fingerprintjs':
specifier: ^4.6.1
version: 4.6.1
'@headlessui/react':
specifier: ^2.1.9
version: 2.1.9(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
@@ -914,6 +917,9 @@ packages:
resolution: {integrity: sha512-ulqQu4KMr1/sTFIYvqSdegHT8NIkt66tFAkugGnHA+1WAfEn6hMzNR+svjXGFRVLnapxvej67Z/LwchFrnLBUg==}
engines: {node: '>=18.0.0', npm: '>=9.0.0'}
'@fingerprintjs/fingerprintjs@4.6.1':
resolution: {integrity: sha512-62TPnX6fXXMlxS7SOR3DJWEOKab7rCALwSWkuKWYMRrnsZ/jD9Ju4CUyy9VWDUYuhQ2ZW1RGLwOZJXTXR6K1pg==}
'@floating-ui/core@1.6.8':
resolution: {integrity: sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==}
@@ -5429,6 +5435,10 @@ snapshots:
'@faker-js/faker@9.2.0': {}
'@fingerprintjs/fingerprintjs@4.6.1':
dependencies:
tslib: 2.8.1
'@floating-ui/core@1.6.8':
dependencies:
'@floating-ui/utils': 0.2.8
@@ -5477,7 +5487,7 @@ snapshots:
'@formatjs/intl-localematcher@0.5.4':
dependencies:
tslib: 2.7.0
tslib: 2.8.1
'@formatjs/intl-localematcher@0.5.8':
dependencies:
@@ -5920,7 +5930,7 @@ snapshots:
'@swc/helpers@0.5.13':
dependencies:
tslib: 2.7.0
tslib: 2.8.1
'@swc/helpers@0.5.15':
dependencies:
@@ -5929,7 +5939,7 @@ snapshots:
'@swc/helpers@0.5.5':
dependencies:
'@swc/counter': 0.1.3
tslib: 2.7.0
tslib: 2.8.1
'@tailwindcss/forms@0.5.3(tailwindcss@3.4.14)':
dependencies:
@@ -7107,7 +7117,7 @@ snapshots:
debug: 4.3.7(supports-color@5.5.0)
enhanced-resolve: 5.17.1
eslint: 8.57.1
eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.1))(eslint@8.57.1)
eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1)
fast-glob: 3.3.2
get-tsconfig: 4.8.0
is-bun-module: 1.1.0
@@ -7120,7 +7130,7 @@ snapshots:
- eslint-import-resolver-webpack
- supports-color
eslint-module-utils@2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.1))(eslint@8.57.1):
eslint-module-utils@2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1):
dependencies:
debug: 3.2.7(supports-color@8.1.1)
optionalDependencies:
@@ -7141,7 +7151,7 @@ snapshots:
doctrine: 2.1.0
eslint: 8.57.1
eslint-import-resolver-node: 0.3.9
eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.1))(eslint@8.57.1)
eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1)
hasown: 2.0.2
is-core-module: 2.15.1
is-glob: 4.0.3