mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-12 15:57:32 +00:00
improve password handling
This commit is contained in:
@@ -83,6 +83,16 @@ export function RegisterPasskey({
|
||||
return;
|
||||
}
|
||||
|
||||
if ("error" in resp && resp.error) {
|
||||
setError(resp.error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!("passkeyId" in resp)) {
|
||||
setError("An error on registering passkey");
|
||||
return;
|
||||
}
|
||||
|
||||
const passkeyId = resp.passkeyId;
|
||||
const options: CredentialCreationOptions =
|
||||
(resp.publicKeyCredentialCreationOptions as CredentialCreationOptions) ??
|
||||
@@ -92,6 +102,7 @@ export function RegisterPasskey({
|
||||
setError("An error on registering passkey");
|
||||
return;
|
||||
}
|
||||
|
||||
options.publicKey.challenge = coerceToArrayBuffer(
|
||||
options.publicKey.challenge,
|
||||
"challenge",
|
||||
|
@@ -5,6 +5,7 @@ import {
|
||||
getLoginSettings,
|
||||
getSession,
|
||||
getUserByID,
|
||||
listAuthenticationMethodTypes,
|
||||
registerPasskey,
|
||||
verifyPasskeyRegistration as zitadelVerifyPasskeyRegistration,
|
||||
} from "@/lib/zitadel";
|
||||
@@ -14,7 +15,8 @@ import {
|
||||
RegisterPasskeyResponse,
|
||||
VerifyPasskeyRegistrationRequestSchema,
|
||||
} from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
||||
import { headers } from "next/headers";
|
||||
import crypto from "crypto";
|
||||
import { cookies, headers } from "next/headers";
|
||||
import { userAgent } from "next/server";
|
||||
import { getNextUrl } from "../client";
|
||||
import {
|
||||
@@ -22,6 +24,7 @@ import {
|
||||
getSessionCookieById,
|
||||
getSessionCookieByLoginName,
|
||||
} from "../cookies";
|
||||
import { getFingerprintId } from "../fingerprint";
|
||||
import { getServiceUrlFromHeaders } from "../service-url";
|
||||
import { checkEmailVerification } from "../verify-helper";
|
||||
import { setSessionAndUpdateCookie } from "./cookie";
|
||||
@@ -39,7 +42,7 @@ type RegisterPasskeyCommand = {
|
||||
|
||||
export async function registerPasskeyLink(
|
||||
command: RegisterPasskeyCommand,
|
||||
): Promise<RegisterPasskeyResponse> {
|
||||
): Promise<RegisterPasskeyResponse | { error: string }> {
|
||||
const { sessionId } = command;
|
||||
|
||||
const _headers = await headers();
|
||||
@@ -57,6 +60,43 @@ export async function registerPasskeyLink(
|
||||
sessionToken: sessionCookie.token,
|
||||
});
|
||||
|
||||
if (!session?.session?.factors?.user?.id) {
|
||||
return { error: "Could not determine user from session" };
|
||||
}
|
||||
|
||||
const authmethods = await listAuthenticationMethodTypes({
|
||||
serviceUrl,
|
||||
userId: session?.session?.factors?.user?.id,
|
||||
});
|
||||
|
||||
// if the user has no authmethods set, we need to check if the user was verified
|
||||
// users are redirected from /authenticator/set to /password/set
|
||||
if (authmethods.authMethodTypes.length !== 0) {
|
||||
return {
|
||||
error:
|
||||
"You have to provide a code or have a valid User Verification Check",
|
||||
};
|
||||
}
|
||||
|
||||
// check if a verification was done earlier
|
||||
const cookiesList = await cookies();
|
||||
const userAgentId = await getFingerprintId();
|
||||
|
||||
const verificationCheck = crypto
|
||||
.createHash("sha256")
|
||||
.update(`${user.userId}:${userAgentId}`)
|
||||
.digest("hex");
|
||||
|
||||
const cookieValue = await cookiesList.get("verificationCheck")?.value;
|
||||
|
||||
if (!cookieValue) {
|
||||
return { error: "User Verification Check has to be done" };
|
||||
}
|
||||
|
||||
if (cookieValue !== verificationCheck) {
|
||||
return { error: "User Verification Check has to be done" };
|
||||
}
|
||||
|
||||
const [hostname, port] = host.split(":");
|
||||
|
||||
if (!hostname) {
|
||||
|
@@ -13,7 +13,6 @@ import {
|
||||
listAuthenticationMethodTypes,
|
||||
listUsers,
|
||||
passwordReset,
|
||||
setPassword,
|
||||
setUserPassword,
|
||||
} from "@/lib/zitadel";
|
||||
import { ConnectError, create } from "@zitadel/client";
|
||||
@@ -25,13 +24,12 @@ import {
|
||||
} from "@zitadel/proto/zitadel/session/v2/session_service_pb";
|
||||
import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
|
||||
import { User, UserState } from "@zitadel/proto/zitadel/user/v2/user_pb";
|
||||
import {
|
||||
AuthenticationMethodType,
|
||||
SetPasswordRequestSchema,
|
||||
} from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
||||
import { headers } from "next/headers";
|
||||
import { SetPasswordRequestSchema } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
||||
import crypto from "crypto";
|
||||
import { cookies, headers } from "next/headers";
|
||||
import { getNextUrl } from "../client";
|
||||
import { getSessionCookieById, getSessionCookieByLoginName } from "../cookies";
|
||||
import { getFingerprintId } from "../fingerprint";
|
||||
import { getServiceUrlFromHeaders } from "../service-url";
|
||||
import {
|
||||
checkEmailVerification,
|
||||
@@ -297,6 +295,7 @@ export async function sendPassword(command: UpdateSessionCommand) {
|
||||
return { redirect: url };
|
||||
}
|
||||
|
||||
// this function lets users with code set a password or users with valid User Verification Check
|
||||
export async function changePassword(command: {
|
||||
code?: string;
|
||||
userId: string;
|
||||
@@ -316,11 +315,50 @@ export async function changePassword(command: {
|
||||
}
|
||||
const userId = user.userId;
|
||||
|
||||
if (user.state === UserState.INITIAL) {
|
||||
return { error: "User Initial State is not supported" };
|
||||
}
|
||||
|
||||
// check if the user has no password set in order to set a password
|
||||
if (!command.code) {
|
||||
const authmethods = await listAuthenticationMethodTypes({
|
||||
serviceUrl,
|
||||
userId,
|
||||
});
|
||||
|
||||
// if the user has no authmethods set, we need to check if the user was verified
|
||||
// users are redirected from /authenticator/set to /password/set
|
||||
if (authmethods.authMethodTypes.length !== 0) {
|
||||
return {
|
||||
error:
|
||||
"You have to provide a code or have a valid User Verification Check",
|
||||
};
|
||||
}
|
||||
|
||||
// check if a verification was done earlier
|
||||
const cookiesList = await cookies();
|
||||
const userAgentId = await getFingerprintId();
|
||||
|
||||
const verificationCheck = crypto
|
||||
.createHash("sha256")
|
||||
.update(`${user.userId}:${userAgentId}`)
|
||||
.digest("hex");
|
||||
|
||||
const cookieValue = await cookiesList.get("verificationCheck")?.value;
|
||||
|
||||
if (!cookieValue) {
|
||||
return { error: "User Verification Check has to be done" };
|
||||
}
|
||||
|
||||
if (cookieValue !== verificationCheck) {
|
||||
return { error: "User Verification Check has to be done" };
|
||||
}
|
||||
}
|
||||
|
||||
return setUserPassword({
|
||||
serviceUrl,
|
||||
userId,
|
||||
password: command.password,
|
||||
user,
|
||||
code: command.code,
|
||||
});
|
||||
}
|
||||
@@ -366,37 +404,6 @@ export async function checkSessionAndSetPassword({
|
||||
return { error: "Could not load auth methods" };
|
||||
}
|
||||
|
||||
const requiredAuthMethodsForForceMFA = [
|
||||
AuthenticationMethodType.OTP_EMAIL,
|
||||
AuthenticationMethodType.OTP_SMS,
|
||||
AuthenticationMethodType.TOTP,
|
||||
AuthenticationMethodType.U2F,
|
||||
];
|
||||
|
||||
const hasNoMFAMethods = requiredAuthMethodsForForceMFA.every(
|
||||
(method) => !authmethods.authMethodTypes.includes(method),
|
||||
);
|
||||
|
||||
const loginSettings = await getLoginSettings({
|
||||
serviceUrl,
|
||||
organization: session.factors.user.organizationId,
|
||||
});
|
||||
|
||||
const forceMfa = !!(
|
||||
loginSettings?.forceMfa || loginSettings?.forceMfaLocalOnly
|
||||
);
|
||||
|
||||
// if the user has no MFA but MFA is enforced, we can set a password otherwise we use the token of the user
|
||||
if (forceMfa && hasNoMFAMethods) {
|
||||
return setPassword({ serviceUrl, payload }).catch((error) => {
|
||||
// throw error if failed precondition (ex. User is not yet initialized)
|
||||
if (error.code === 9 && error.message) {
|
||||
return { error: "Failed precondition" };
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const transport = async (serviceUrl: string, token: string) => {
|
||||
return createServerTransport(token, {
|
||||
baseUrl: serviceUrl,
|
||||
@@ -408,10 +415,7 @@ export async function checkSessionAndSetPassword({
|
||||
return createUserServiceClient(transportPromise);
|
||||
};
|
||||
|
||||
const selfService = await myUserService(
|
||||
serviceUrl,
|
||||
`${sessionCookie.token}`,
|
||||
);
|
||||
const selfService = await myUserService(serviceUrl, `${sessionCookie.token}`);
|
||||
|
||||
return selfService
|
||||
.setPassword(
|
||||
@@ -429,4 +433,3 @@ export async function checkSessionAndSetPassword({
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -29,11 +29,7 @@ import {
|
||||
SearchQuery,
|
||||
SearchQuerySchema,
|
||||
} from "@zitadel/proto/zitadel/user/v2/query_pb";
|
||||
import {
|
||||
SendInviteCodeSchema,
|
||||
User,
|
||||
UserState,
|
||||
} from "@zitadel/proto/zitadel/user/v2/user_pb";
|
||||
import { SendInviteCodeSchema } from "@zitadel/proto/zitadel/user/v2/user_pb";
|
||||
import {
|
||||
AddHumanUserRequest,
|
||||
ResendEmailCodeRequest,
|
||||
@@ -45,10 +41,8 @@ import {
|
||||
VerifyPasskeyRegistrationRequest,
|
||||
VerifyU2FRegistrationRequest,
|
||||
} from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
||||
import crypto from "crypto";
|
||||
import { unstable_cacheLife as cacheLife } from "next/cache";
|
||||
import { cookies } from "next/headers";
|
||||
import { getFingerprintId, getUserAgent } from "./fingerprint";
|
||||
import { getUserAgent } from "./fingerprint";
|
||||
import { createServiceForHost } from "./service";
|
||||
|
||||
const useCache = process.env.DEBUG !== "true";
|
||||
@@ -1172,13 +1166,11 @@ export async function setUserPassword({
|
||||
serviceUrl,
|
||||
userId,
|
||||
password,
|
||||
user,
|
||||
code,
|
||||
}: {
|
||||
serviceUrl: string;
|
||||
userId: string;
|
||||
password: string;
|
||||
user: User;
|
||||
code?: string;
|
||||
}) {
|
||||
let payload = create(SetPasswordRequestSchema, {
|
||||
@@ -1188,41 +1180,6 @@ export async function setUserPassword({
|
||||
},
|
||||
});
|
||||
|
||||
// check if the user has no password set in order to set a password
|
||||
if (!code) {
|
||||
const authmethods = await listAuthenticationMethodTypes({
|
||||
serviceUrl,
|
||||
userId,
|
||||
});
|
||||
|
||||
// if the user has no authmethods set, we can set a password otherwise we need a code
|
||||
if (
|
||||
!(authmethods.authMethodTypes.length === 0) &&
|
||||
user.state !== UserState.INITIAL
|
||||
) {
|
||||
// check if a verification was done earlier
|
||||
|
||||
const cookiesList = await cookies();
|
||||
|
||||
const userAgentId = await getFingerprintId();
|
||||
|
||||
const verificationCheck = crypto
|
||||
.createHash("sha256")
|
||||
.update(`${user.userId}:${userAgentId}`)
|
||||
.digest("hex");
|
||||
|
||||
await cookiesList.set({
|
||||
name: "verificationCheck",
|
||||
value: verificationCheck,
|
||||
httpOnly: true,
|
||||
path: "/",
|
||||
maxAge: 300, // 5 minutes
|
||||
});
|
||||
|
||||
return { error: "Provide a code to set a password" };
|
||||
}
|
||||
}
|
||||
|
||||
if (code) {
|
||||
payload = {
|
||||
...payload,
|
||||
|
Reference in New Issue
Block a user