2024-08-29 16:25:44 +02:00
|
|
|
"use server";
|
|
|
|
|
|
2024-09-18 14:13:04 +02:00
|
|
|
import { create } from "@zitadel/client";
|
|
|
|
|
import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
|
2024-09-11 15:27:31 +02:00
|
|
|
import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
2024-08-29 16:25:44 +02:00
|
|
|
import { headers } from "next/headers";
|
2024-09-05 13:48:33 +02:00
|
|
|
import { redirect } from "next/navigation";
|
2024-09-18 14:13:04 +02:00
|
|
|
import { createSessionAndUpdateCookie } from "../../utils/session";
|
2024-08-29 16:25:44 +02:00
|
|
|
import { idpTypeToSlug } from "../idp";
|
|
|
|
|
import {
|
|
|
|
|
getActiveIdentityProviders,
|
|
|
|
|
getLoginSettings,
|
2024-09-13 14:47:33 +02:00
|
|
|
getOrgsByDomain,
|
2024-08-29 16:25:44 +02:00
|
|
|
listAuthenticationMethodTypes,
|
|
|
|
|
listUsers,
|
|
|
|
|
startIdentityProviderFlow,
|
|
|
|
|
} from "../zitadel";
|
|
|
|
|
|
2024-08-30 09:52:42 +02:00
|
|
|
export type SendLoginnameCommand = {
|
2024-08-29 16:25:44 +02:00
|
|
|
loginName: string;
|
|
|
|
|
authRequestId?: string;
|
|
|
|
|
organization?: string;
|
|
|
|
|
};
|
|
|
|
|
|
2024-09-13 14:47:33 +02:00
|
|
|
const ORG_SUFFIX_REGEX = /(?<=@)(.+)/;
|
|
|
|
|
|
2024-09-05 13:38:03 +02:00
|
|
|
export async function sendLoginname(command: SendLoginnameCommand) {
|
2024-08-29 16:25:44 +02:00
|
|
|
const users = await listUsers({
|
2024-09-11 09:27:04 +02:00
|
|
|
loginName: command.loginName,
|
2024-09-05 13:38:03 +02:00
|
|
|
organizationId: command.organization,
|
2024-08-29 16:25:44 +02:00
|
|
|
});
|
|
|
|
|
|
2024-09-11 15:27:31 +02:00
|
|
|
const loginSettings = await getLoginSettings(command.organization);
|
|
|
|
|
|
2024-09-13 14:01:03 +02:00
|
|
|
const redirectUserToSingleIDPIfAvailable = async () => {
|
|
|
|
|
const identityProviders = await getActiveIdentityProviders(
|
|
|
|
|
command.organization,
|
|
|
|
|
).then((resp) => {
|
|
|
|
|
return resp.identityProviders;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (identityProviders.length === 1) {
|
|
|
|
|
const host = headers().get("host");
|
|
|
|
|
const identityProviderType = identityProviders[0].type;
|
|
|
|
|
|
|
|
|
|
const provider = idpTypeToSlug(identityProviderType);
|
|
|
|
|
|
|
|
|
|
const params = new URLSearchParams();
|
|
|
|
|
|
|
|
|
|
if (command.authRequestId) {
|
|
|
|
|
params.set("authRequestId", command.authRequestId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (command.organization) {
|
|
|
|
|
params.set("organization", command.organization);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const resp = await startIdentityProviderFlow({
|
|
|
|
|
idpId: identityProviders[0].id,
|
|
|
|
|
urls: {
|
|
|
|
|
successUrl:
|
|
|
|
|
`${host}/idp/${provider}/success?` + new URLSearchParams(params),
|
|
|
|
|
failureUrl:
|
|
|
|
|
`${host}/idp/${provider}/failure?` + new URLSearchParams(params),
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (resp?.nextStep.case === "authUrl") {
|
|
|
|
|
return redirect(resp.nextStep.value);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2024-08-29 16:25:44 +02:00
|
|
|
if (users.details?.totalResult == BigInt(1) && users.result[0].userId) {
|
|
|
|
|
const userId = users.result[0].userId;
|
2024-09-18 14:13:04 +02:00
|
|
|
|
|
|
|
|
const checks = create(ChecksSchema, {
|
|
|
|
|
user: { search: { case: "userId", value: userId } },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const session = await createSessionAndUpdateCookie(
|
|
|
|
|
checks,
|
2024-08-29 16:25:44 +02:00
|
|
|
undefined,
|
2024-09-05 13:38:03 +02:00
|
|
|
command.authRequestId,
|
2024-08-29 16:25:44 +02:00
|
|
|
);
|
|
|
|
|
|
2024-08-30 09:52:42 +02:00
|
|
|
if (!session.factors?.user?.id) {
|
2024-09-16 14:41:38 +02:00
|
|
|
return { error: "Could not create session for user" };
|
2024-08-29 16:25:44 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const methods = await listAuthenticationMethodTypes(
|
|
|
|
|
session.factors?.user?.id,
|
|
|
|
|
);
|
|
|
|
|
|
2024-09-11 15:27:31 +02:00
|
|
|
if (!methods.authMethodTypes || !methods.authMethodTypes.length) {
|
2024-09-16 14:41:38 +02:00
|
|
|
return {
|
|
|
|
|
error:
|
|
|
|
|
"User has no available authentication methods. Contact your administrator to setup authentication for the requested user.",
|
|
|
|
|
};
|
2024-09-11 15:27:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (methods.authMethodTypes.length == 1) {
|
|
|
|
|
const method = methods.authMethodTypes[0];
|
|
|
|
|
switch (method) {
|
|
|
|
|
case AuthenticationMethodType.PASSWORD: // user has only password as auth method
|
|
|
|
|
const paramsPassword: any = {
|
|
|
|
|
loginName: session.factors?.user?.loginName,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// TODO: does this have to be checked in loginSettings.allowDomainDiscovery
|
|
|
|
|
|
|
|
|
|
if (command.organization || session.factors?.user?.organizationId) {
|
|
|
|
|
paramsPassword.organization =
|
|
|
|
|
command.organization ?? session.factors?.user?.organizationId;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (command.authRequestId) {
|
|
|
|
|
paramsPassword.authRequestId = command.authRequestId;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return redirect("/password?" + new URLSearchParams(paramsPassword));
|
|
|
|
|
case AuthenticationMethodType.PASSKEY: // AuthenticationMethodType.AUTHENTICATION_METHOD_TYPE_PASSKEY
|
|
|
|
|
const paramsPasskey: any = { loginName: command.loginName };
|
|
|
|
|
if (command.authRequestId) {
|
|
|
|
|
paramsPasskey.authRequestId = command.authRequestId;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (command.organization || session.factors?.user?.organizationId) {
|
|
|
|
|
paramsPasskey.organization =
|
|
|
|
|
command.organization ?? session.factors?.user?.organizationId;
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-16 12:04:43 +02:00
|
|
|
return redirect("/passkey?" + new URLSearchParams(paramsPasskey));
|
2024-09-11 15:27:31 +02:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// prefer passkey in favor of other methods
|
|
|
|
|
if (methods.authMethodTypes.includes(AuthenticationMethodType.PASSKEY)) {
|
|
|
|
|
const passkeyParams: any = {
|
|
|
|
|
loginName: command.loginName,
|
|
|
|
|
altPassword: `${methods.authMethodTypes.includes(1)}`, // show alternative password option
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (command.authRequestId) {
|
|
|
|
|
passkeyParams.authRequestId = command.authRequestId;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (command.organization || session.factors?.user?.organizationId) {
|
|
|
|
|
passkeyParams.organization =
|
|
|
|
|
command.organization ?? session.factors?.user?.organizationId;
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-16 12:04:43 +02:00
|
|
|
return redirect("/passkey?" + new URLSearchParams(passkeyParams));
|
2024-09-11 15:27:31 +02:00
|
|
|
} else if (
|
|
|
|
|
methods.authMethodTypes.includes(AuthenticationMethodType.IDP)
|
|
|
|
|
) {
|
|
|
|
|
// TODO: redirect user to idp
|
2024-09-13 14:01:03 +02:00
|
|
|
await redirectUserToSingleIDPIfAvailable();
|
2024-09-11 15:27:31 +02:00
|
|
|
} else if (
|
|
|
|
|
methods.authMethodTypes.includes(AuthenticationMethodType.PASSWORD)
|
|
|
|
|
) {
|
|
|
|
|
// user has no passkey setup and login settings allow passkeys
|
|
|
|
|
const paramsPasswordDefault: any = { loginName: command.loginName };
|
|
|
|
|
|
|
|
|
|
if (command.authRequestId) {
|
|
|
|
|
paramsPasswordDefault.authRequestId = command.authRequestId;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (command.organization || session.factors?.user?.organizationId) {
|
|
|
|
|
paramsPasswordDefault.organization =
|
|
|
|
|
command.organization ?? session.factors?.user?.organizationId;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return redirect(
|
|
|
|
|
"/password?" + new URLSearchParams(paramsPasswordDefault),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-08-29 16:25:44 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// user not found, check if register is enabled on organization
|
|
|
|
|
if (loginSettings?.allowRegister && !loginSettings?.allowUsernamePassword) {
|
2024-09-13 14:14:09 +02:00
|
|
|
// TODO: do we need to handle login hints for IDPs here?
|
2024-09-13 14:01:03 +02:00
|
|
|
await redirectUserToSingleIDPIfAvailable();
|
2024-09-11 13:44:15 +02:00
|
|
|
|
2024-09-16 14:41:38 +02:00
|
|
|
return { error: "Could not find user" };
|
2024-08-29 16:25:44 +02:00
|
|
|
} else if (
|
|
|
|
|
loginSettings?.allowRegister &&
|
|
|
|
|
loginSettings?.allowUsernamePassword
|
|
|
|
|
) {
|
2024-09-13 14:47:33 +02:00
|
|
|
let orgToRegisterOn: string | undefined = command.organization;
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
!orgToRegisterOn &&
|
|
|
|
|
command.loginName &&
|
|
|
|
|
ORG_SUFFIX_REGEX.test(command.loginName)
|
|
|
|
|
) {
|
|
|
|
|
const matched = ORG_SUFFIX_REGEX.exec(command.loginName);
|
|
|
|
|
const suffix = matched?.[1] ?? "";
|
|
|
|
|
|
|
|
|
|
// this just returns orgs where the suffix is set as primary domain
|
|
|
|
|
const orgs = await getOrgsByDomain(suffix);
|
|
|
|
|
const orgToCheckForDiscovery =
|
|
|
|
|
orgs.result && orgs.result.length === 1 ? orgs.result[0].id : undefined;
|
|
|
|
|
|
|
|
|
|
const orgLoginSettings = await getLoginSettings(orgToCheckForDiscovery);
|
|
|
|
|
if (orgLoginSettings?.allowDomainDiscovery) {
|
|
|
|
|
orgToRegisterOn = orgToCheckForDiscovery;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-18 14:13:04 +02:00
|
|
|
// TODO: check if ignoreUnknownUsernames or register has a higher priority
|
2024-09-13 14:47:33 +02:00
|
|
|
if (orgToRegisterOn) {
|
2024-09-18 14:13:04 +02:00
|
|
|
const params = new URLSearchParams({ organization: orgToRegisterOn });
|
2024-08-29 16:25:44 +02:00
|
|
|
|
2024-09-18 14:13:04 +02:00
|
|
|
if (command.authRequestId) {
|
|
|
|
|
params.set("authRequestId", command.authRequestId);
|
|
|
|
|
}
|
|
|
|
|
if (command.loginName) {
|
|
|
|
|
params.set("loginName", command.loginName);
|
|
|
|
|
}
|
2024-08-29 16:25:44 +02:00
|
|
|
|
2024-09-18 14:13:04 +02:00
|
|
|
return redirect("/register?" + params);
|
|
|
|
|
}
|
2024-08-29 16:25:44 +02:00
|
|
|
}
|
|
|
|
|
|
2024-09-13 14:01:03 +02:00
|
|
|
if (loginSettings?.ignoreUnknownUsernames) {
|
2024-09-18 14:13:04 +02:00
|
|
|
const paramsPasswordDefault = new URLSearchParams({
|
|
|
|
|
loginName: command.loginName,
|
|
|
|
|
});
|
2024-09-13 14:01:03 +02:00
|
|
|
|
|
|
|
|
if (command.authRequestId) {
|
2024-09-18 14:13:04 +02:00
|
|
|
paramsPasswordDefault.append("authRequestId", command.authRequestId);
|
2024-09-13 14:01:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (command.organization) {
|
2024-09-18 14:13:04 +02:00
|
|
|
paramsPasswordDefault.append("organization", command.organization);
|
2024-09-13 14:01:03 +02:00
|
|
|
}
|
|
|
|
|
|
2024-09-18 14:13:04 +02:00
|
|
|
return redirect("/password?" + paramsPasswordDefault);
|
2024-09-13 14:01:03 +02:00
|
|
|
}
|
|
|
|
|
|
2024-09-18 14:13:04 +02:00
|
|
|
// fallbackToPassword
|
|
|
|
|
|
2024-09-16 14:41:38 +02:00
|
|
|
return { error: "Could not find user" };
|
2024-08-29 16:25:44 +02:00
|
|
|
}
|