mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-12 13:37:35 +00:00
Merge pull request #320 from zitadel/qa
fix: Finish IDP Signup for OIDC flow
This commit is contained in:
@@ -60,9 +60,9 @@ You can already use the current state, and extend it with your needs.
|
|||||||
- [x] GitLab
|
- [x] GitLab
|
||||||
- [x] GitLab Enterprise
|
- [x] GitLab Enterprise
|
||||||
- [x] Azure
|
- [x] Azure
|
||||||
- [ ] Apple
|
- [x] Apple
|
||||||
- [x] Generic OIDC
|
- [x] Generic OIDC
|
||||||
- [ ] Generic OAuth
|
- [x] Generic OAuth
|
||||||
- [ ] Generic JWT
|
- [ ] Generic JWT
|
||||||
- [ ] LDAP
|
- [ ] LDAP
|
||||||
- [ ] SAML SP
|
- [ ] SAML SP
|
||||||
|
@@ -396,3 +396,5 @@ Timebased features like the multifactor init prompt or password expiry, are not
|
|||||||
- Login Settings: multifactor init prompt
|
- Login Settings: multifactor init prompt
|
||||||
- forceMFA on login settings is not checked for IDPs
|
- forceMFA on login settings is not checked for IDPs
|
||||||
- disablePhone / disableEmail from loginSettings will be implemented right after https://github.com/zitadel/zitadel/issues/9016 is merged
|
- disablePhone / disableEmail from loginSettings will be implemented right after https://github.com/zitadel/zitadel/issues/9016 is merged
|
||||||
|
|
||||||
|
Also note that IDP logins are considered as valid MFA. An additional MFA check will be implemented in future if enforced.
|
||||||
|
@@ -3,18 +3,28 @@ import { DynamicTheme } from "@/components/dynamic-theme";
|
|||||||
import { IdpSignin } from "@/components/idp-signin";
|
import { IdpSignin } from "@/components/idp-signin";
|
||||||
import { idpTypeToIdentityProviderType, PROVIDER_MAPPING } from "@/lib/idp";
|
import { idpTypeToIdentityProviderType, PROVIDER_MAPPING } from "@/lib/idp";
|
||||||
import {
|
import {
|
||||||
|
addHuman,
|
||||||
addIDPLink,
|
addIDPLink,
|
||||||
createUser,
|
|
||||||
getBrandingSettings,
|
getBrandingSettings,
|
||||||
getIDPByID,
|
getIDPByID,
|
||||||
|
getLoginSettings,
|
||||||
|
getOrgsByDomain,
|
||||||
listUsers,
|
listUsers,
|
||||||
retrieveIDPIntent,
|
retrieveIDPIntent,
|
||||||
} from "@/lib/zitadel";
|
} from "@/lib/zitadel";
|
||||||
|
import { create } from "@zitadel/client";
|
||||||
import { AutoLinkingOption } from "@zitadel/proto/zitadel/idp/v2/idp_pb";
|
import { AutoLinkingOption } from "@zitadel/proto/zitadel/idp/v2/idp_pb";
|
||||||
|
import { OrganizationSchema } from "@zitadel/proto/zitadel/object/v2/object_pb";
|
||||||
import { BrandingSettings } from "@zitadel/proto/zitadel/settings/v2/branding_settings_pb";
|
import { BrandingSettings } from "@zitadel/proto/zitadel/settings/v2/branding_settings_pb";
|
||||||
|
import {
|
||||||
|
AddHumanUserRequest,
|
||||||
|
AddHumanUserRequestSchema,
|
||||||
|
} from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
||||||
import { getLocale, getTranslations } from "next-intl/server";
|
import { getLocale, getTranslations } from "next-intl/server";
|
||||||
|
|
||||||
async function loginFailed(branding?: BrandingSettings) {
|
const ORG_SUFFIX_REGEX = /(?<=@)(.+)/;
|
||||||
|
|
||||||
|
async function loginFailed(branding?: BrandingSettings, error: string = "") {
|
||||||
const locale = getLocale();
|
const locale = getLocale();
|
||||||
const t = await getTranslations({ locale, namespace: "idp" });
|
const t = await getTranslations({ locale, namespace: "idp" });
|
||||||
|
|
||||||
@@ -22,13 +32,67 @@ async function loginFailed(branding?: BrandingSettings) {
|
|||||||
<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>{t("loginError.title")}</h1>
|
<h1>{t("loginError.title")}</h1>
|
||||||
<div className="w-full">
|
<p className="ztdl-p">{t("loginError.description")}</p>
|
||||||
{<Alert type={AlertType.ALERT}>{t("loginError.title")}</Alert>}
|
{error && (
|
||||||
</div>
|
<div className="w-full">
|
||||||
|
{<Alert type={AlertType.ALERT}>{error}</Alert>}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</DynamicTheme>
|
</DynamicTheme>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loginSuccess(
|
||||||
|
userId: string,
|
||||||
|
idpIntent: { idpIntentId: string; idpIntentToken: string },
|
||||||
|
authRequestId?: string,
|
||||||
|
branding?: BrandingSettings,
|
||||||
|
) {
|
||||||
|
const locale = getLocale();
|
||||||
|
const t = await getTranslations({ locale, namespace: "idp" });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DynamicTheme branding={branding}>
|
||||||
|
<div className="flex flex-col items-center space-y-4">
|
||||||
|
<h1>{t("loginSuccess.title")}</h1>
|
||||||
|
<p className="ztdl-p">{t("loginSuccess.description")}</p>
|
||||||
|
|
||||||
|
<IdpSignin
|
||||||
|
userId={userId}
|
||||||
|
idpIntent={idpIntent}
|
||||||
|
authRequestId={authRequestId}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</DynamicTheme>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function linkingSuccess(
|
||||||
|
userId: string,
|
||||||
|
idpIntent: { idpIntentId: string; idpIntentToken: string },
|
||||||
|
authRequestId?: string,
|
||||||
|
branding?: BrandingSettings,
|
||||||
|
) {
|
||||||
|
const locale = getLocale();
|
||||||
|
const t = await getTranslations({ locale, namespace: "idp" });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DynamicTheme branding={branding}>
|
||||||
|
<div className="flex flex-col items-center space-y-4">
|
||||||
|
<h1>{t("linkingSuccess.title")}</h1>
|
||||||
|
<p className="ztdl-p">{t("linkingSuccess.description")}</p>
|
||||||
|
|
||||||
|
<IdpSignin
|
||||||
|
userId={userId}
|
||||||
|
idpIntent={idpIntent}
|
||||||
|
authRequestId={authRequestId}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</DynamicTheme>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default async function Page(props: {
|
export default async function Page(props: {
|
||||||
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
|
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
|
||||||
params: Promise<{ provider: string }>;
|
params: Promise<{ provider: string }>;
|
||||||
@@ -43,7 +107,7 @@ export default async function Page(props: {
|
|||||||
const branding = await getBrandingSettings(organization);
|
const branding = await getBrandingSettings(organization);
|
||||||
|
|
||||||
if (!provider || !id || !token) {
|
if (!provider || !id || !token) {
|
||||||
return loginFailed(branding);
|
return loginFailed(branding, "IDP context missing");
|
||||||
}
|
}
|
||||||
|
|
||||||
const intent = await retrieveIDPIntent(id, token);
|
const intent = await retrieveIDPIntent(id, token);
|
||||||
@@ -54,24 +118,16 @@ export default async function Page(props: {
|
|||||||
if (userId && !link) {
|
if (userId && !link) {
|
||||||
// TODO: update user if idp.options.isAutoUpdate is true
|
// TODO: update user if idp.options.isAutoUpdate is true
|
||||||
|
|
||||||
return (
|
return loginSuccess(
|
||||||
<DynamicTheme branding={branding}>
|
userId,
|
||||||
<div className="flex flex-col items-center space-y-4">
|
{ idpIntentId: id, idpIntentToken: token },
|
||||||
<h1>{t("loginSuccess.title")}</h1>
|
authRequestId,
|
||||||
<div>{t("loginSuccess.description")}</div>
|
branding,
|
||||||
|
|
||||||
<IdpSignin
|
|
||||||
userId={userId}
|
|
||||||
idpIntent={{ idpIntentId: id, idpIntentToken: token }}
|
|
||||||
authRequestId={authRequestId}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</DynamicTheme>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!idpInformation) {
|
if (!idpInformation) {
|
||||||
return loginFailed(branding);
|
return loginFailed(branding, "IDP information missing");
|
||||||
}
|
}
|
||||||
|
|
||||||
const idp = await getIDPByID(idpInformation.idpId);
|
const idp = await getIDPByID(idpInformation.idpId);
|
||||||
@@ -135,28 +191,65 @@ export default async function Page(props: {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (idpLink) {
|
if (idpLink) {
|
||||||
return (
|
return linkingSuccess(
|
||||||
// TODO: possibily login user now
|
foundUser.userId,
|
||||||
<DynamicTheme branding={branding}>
|
{ idpIntentId: id, idpIntentToken: token },
|
||||||
<div className="flex flex-col items-center space-y-4">
|
authRequestId,
|
||||||
<h1>{t("linkingSuccess.title")}</h1>
|
branding,
|
||||||
<div>{t("linkingSuccess.description")}</div>
|
|
||||||
</div>
|
|
||||||
</DynamicTheme>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options?.isCreationAllowed && options.isAutoCreation) {
|
if (options?.isCreationAllowed && options.isAutoCreation) {
|
||||||
const newUser = await createUser(providerType, idpInformation);
|
let orgToRegisterOn: string | undefined = organization;
|
||||||
|
|
||||||
|
let userData: AddHumanUserRequest =
|
||||||
|
PROVIDER_MAPPING[providerType](idpInformation);
|
||||||
|
|
||||||
|
if (
|
||||||
|
!orgToRegisterOn &&
|
||||||
|
userData.username && // username or email?
|
||||||
|
ORG_SUFFIX_REGEX.test(userData.username)
|
||||||
|
) {
|
||||||
|
const matched = ORG_SUFFIX_REGEX.exec(userData.username);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (orgToRegisterOn) {
|
||||||
|
const organizationSchema = create(OrganizationSchema, {
|
||||||
|
org: { case: "orgId", value: orgToRegisterOn },
|
||||||
|
});
|
||||||
|
|
||||||
|
userData = create(AddHumanUserRequestSchema, {
|
||||||
|
...userData,
|
||||||
|
organization: organizationSchema,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const newUser = await addHuman(userData);
|
||||||
|
|
||||||
if (newUser) {
|
if (newUser) {
|
||||||
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>{t("registerSuccess.title")}</h1>
|
<h1>{t("registerSuccess.title")}</h1>
|
||||||
<div>{t("registerSuccess.description")}</div>
|
<p className="ztdl-p">{t("registerSuccess.description")}</p>
|
||||||
|
<IdpSignin
|
||||||
|
userId={newUser.userId}
|
||||||
|
idpIntent={{ idpIntentId: id, idpIntentToken: token }}
|
||||||
|
authRequestId={authRequestId}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</DynamicTheme>
|
</DynamicTheme>
|
||||||
);
|
);
|
||||||
@@ -164,5 +257,5 @@ export default async function Page(props: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// return login failed if no linking or creation is allowed and no user was found
|
// return login failed if no linking or creation is allowed and no user was found
|
||||||
return loginFailed;
|
return loginFailed(branding, "No user found");
|
||||||
}
|
}
|
||||||
|
@@ -61,7 +61,7 @@ export default async function Page(props: {
|
|||||||
<SignInWithIdp
|
<SignInWithIdp
|
||||||
identityProviders={identityProviders}
|
identityProviders={identityProviders}
|
||||||
authRequestId={authRequestId}
|
authRequestId={authRequestId}
|
||||||
organization={organization ?? defaultOrganization} // use the organization from the searchParams here otherwise fallback to the default organization
|
organization={organization}
|
||||||
></SignInWithIdp>
|
></SignInWithIdp>
|
||||||
)}
|
)}
|
||||||
</UsernameForm>
|
</UsernameForm>
|
||||||
|
@@ -109,9 +109,10 @@ async function isSessionValid(session: Session): Promise<boolean> {
|
|||||||
const otpSms = session.factors.otpSms?.verifiedAt;
|
const otpSms = session.factors.otpSms?.verifiedAt;
|
||||||
const totp = session.factors.totp?.verifiedAt;
|
const totp = session.factors.totp?.verifiedAt;
|
||||||
const webAuthN = session.factors.webAuthN?.verifiedAt;
|
const webAuthN = session.factors.webAuthN?.verifiedAt;
|
||||||
|
const idp = session.factors.intent?.verifiedAt; // TODO: forceMFA should not consider this as valid factor
|
||||||
|
|
||||||
// must have one single check
|
// must have one single check
|
||||||
mfaValid = !!(otpEmail || otpSms || totp || webAuthN);
|
mfaValid = !!(otpEmail || otpSms || totp || webAuthN || idp);
|
||||||
if (!mfaValid) {
|
if (!mfaValid) {
|
||||||
console.warn("Session has no valid multifactor", session.factors);
|
console.warn("Session has no valid multifactor", session.factors);
|
||||||
}
|
}
|
||||||
@@ -207,8 +208,11 @@ export async function GET(request: NextRequest) {
|
|||||||
|
|
||||||
const isValid = await isSessionValid(selectedSession);
|
const isValid = await isSessionValid(selectedSession);
|
||||||
|
|
||||||
|
console.log("Session is valid:", isValid);
|
||||||
|
|
||||||
if (!isValid && selectedSession.factors?.user) {
|
if (!isValid && selectedSession.factors?.user) {
|
||||||
// if the session is not valid anymore, we need to redirect the user to re-authenticate
|
// if the session is not valid anymore, we need to redirect the user to re-authenticate /
|
||||||
|
// TODO: handle IDP intent direcly if available
|
||||||
const command: SendLoginnameCommand = {
|
const command: SendLoginnameCommand = {
|
||||||
loginName: selectedSession.factors.user?.loginName,
|
loginName: selectedSession.factors.user?.loginName,
|
||||||
organization: selectedSession.factors?.user?.organizationId,
|
organization: selectedSession.factors?.user?.organizationId,
|
||||||
|
@@ -21,7 +21,7 @@ export function IdpSignin({
|
|||||||
idpIntent: { idpIntentId, idpIntentToken },
|
idpIntent: { idpIntentId, idpIntentToken },
|
||||||
authRequestId,
|
authRequestId,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -55,8 +55,8 @@ export function IdpSignin({
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center">
|
<div className="flex items-center justify-center py-4">
|
||||||
{loading && <Spinner />}
|
{loading && <Spinner className="h-5 w-5" />}
|
||||||
{error && (
|
{error && (
|
||||||
<div className="py-4">
|
<div className="py-4">
|
||||||
<Alert>{error}</Alert>
|
<Alert>{error}</Alert>
|
||||||
|
@@ -91,47 +91,44 @@ export async function createSessionForIdpAndUpdateCookie(
|
|||||||
lifetime,
|
lifetime,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (createdSession) {
|
if (!createdSession) {
|
||||||
return getSession({
|
|
||||||
sessionId: createdSession.sessionId,
|
|
||||||
sessionToken: createdSession.sessionToken,
|
|
||||||
}).then((response) => {
|
|
||||||
if (response?.session && response.session?.factors?.user?.loginName) {
|
|
||||||
const sessionCookie: CustomCookieData = {
|
|
||||||
id: createdSession.sessionId,
|
|
||||||
token: createdSession.sessionToken,
|
|
||||||
creationTs: response.session.creationDate
|
|
||||||
? `${timestampMs(response.session.creationDate)}`
|
|
||||||
: "",
|
|
||||||
expirationTs: response.session.expirationDate
|
|
||||||
? `${timestampMs(response.session.expirationDate)}`
|
|
||||||
: "",
|
|
||||||
changeTs: response.session.changeDate
|
|
||||||
? `${timestampMs(response.session.changeDate)}`
|
|
||||||
: "",
|
|
||||||
loginName: response.session.factors.user.loginName ?? "",
|
|
||||||
organization: response.session.factors.user.organizationId ?? "",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (authRequestId) {
|
|
||||||
sessionCookie.authRequestId = authRequestId;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.session.factors.user.organizationId) {
|
|
||||||
sessionCookie.organization =
|
|
||||||
response.session.factors.user.organizationId;
|
|
||||||
}
|
|
||||||
|
|
||||||
return addSessionToCookie(sessionCookie).then(() => {
|
|
||||||
return response.session as Session;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
throw "could not get session or session does not have loginName";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
throw "Could not create session";
|
throw "Could not create session";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { session } = await getSession({
|
||||||
|
sessionId: createdSession.sessionId,
|
||||||
|
sessionToken: createdSession.sessionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!session || !session.factors?.user?.loginName) {
|
||||||
|
throw "Could not retrieve session";
|
||||||
|
}
|
||||||
|
|
||||||
|
const sessionCookie: CustomCookieData = {
|
||||||
|
id: createdSession.sessionId,
|
||||||
|
token: createdSession.sessionToken,
|
||||||
|
creationTs: session.creationDate
|
||||||
|
? `${timestampMs(session.creationDate)}`
|
||||||
|
: "",
|
||||||
|
expirationTs: session.expirationDate
|
||||||
|
? `${timestampMs(session.expirationDate)}`
|
||||||
|
: "",
|
||||||
|
changeTs: session.changeDate ? `${timestampMs(session.changeDate)}` : "",
|
||||||
|
loginName: session.factors.user.loginName ?? "",
|
||||||
|
organization: session.factors.user.organizationId ?? "",
|
||||||
|
};
|
||||||
|
|
||||||
|
if (authRequestId) {
|
||||||
|
sessionCookie.authRequestId = authRequestId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (session.factors.user.organizationId) {
|
||||||
|
sessionCookie.organization = session.factors.user.organizationId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return addSessionToCookie(sessionCookie).then(() => {
|
||||||
|
return session as Session;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SessionWithChallenges = Session & {
|
export type SessionWithChallenges = Session & {
|
||||||
|
@@ -10,8 +10,8 @@ import {
|
|||||||
import { createServerTransport } from "@zitadel/node";
|
import { createServerTransport } from "@zitadel/node";
|
||||||
import { RequestChallenges } from "@zitadel/proto/zitadel/session/v2/challenge_pb";
|
import { RequestChallenges } from "@zitadel/proto/zitadel/session/v2/challenge_pb";
|
||||||
import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
|
import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
|
||||||
import { IDPInformation } from "@zitadel/proto/zitadel/user/v2/idp_pb";
|
|
||||||
import {
|
import {
|
||||||
|
AddHumanUserRequest,
|
||||||
RetrieveIdentityProviderIntentRequest,
|
RetrieveIdentityProviderIntentRequest,
|
||||||
SetPasswordRequest,
|
SetPasswordRequest,
|
||||||
SetPasswordRequestSchema,
|
SetPasswordRequestSchema,
|
||||||
@@ -23,7 +23,6 @@ import { create, Duration } from "@zitadel/client";
|
|||||||
import { TextQueryMethod } from "@zitadel/proto/zitadel/object/v2/object_pb";
|
import { TextQueryMethod } from "@zitadel/proto/zitadel/object/v2/object_pb";
|
||||||
import { CreateCallbackRequest } from "@zitadel/proto/zitadel/oidc/v2/oidc_service_pb";
|
import { CreateCallbackRequest } from "@zitadel/proto/zitadel/oidc/v2/oidc_service_pb";
|
||||||
import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb";
|
import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb";
|
||||||
import { IdentityProviderType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
|
|
||||||
import type { RedirectURLsJson } from "@zitadel/proto/zitadel/user/v2/idp_pb";
|
import type { RedirectURLsJson } from "@zitadel/proto/zitadel/user/v2/idp_pb";
|
||||||
import {
|
import {
|
||||||
NotificationType,
|
NotificationType,
|
||||||
@@ -39,7 +38,6 @@ import {
|
|||||||
UserState,
|
UserState,
|
||||||
} from "@zitadel/proto/zitadel/user/v2/user_pb";
|
} from "@zitadel/proto/zitadel/user/v2/user_pb";
|
||||||
import { unstable_cacheLife as cacheLife } from "next/cache";
|
import { unstable_cacheLife as cacheLife } from "next/cache";
|
||||||
import { PROVIDER_MAPPING } from "./idp";
|
|
||||||
|
|
||||||
const transport = createServerTransport(
|
const transport = createServerTransport(
|
||||||
process.env.ZITADEL_SERVICE_USER_TOKEN!,
|
process.env.ZITADEL_SERVICE_USER_TOKEN!,
|
||||||
@@ -249,6 +247,10 @@ export async function addHumanUser({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function addHuman(request: AddHumanUserRequest) {
|
||||||
|
return userService.addHumanUser(request);
|
||||||
|
}
|
||||||
|
|
||||||
export async function verifyTOTPRegistration(code: string, userId: string) {
|
export async function verifyTOTPRegistration(code: string, userId: string) {
|
||||||
return userService.verifyTOTPRegistration({ code, userId }, {});
|
return userService.verifyTOTPRegistration({ code, userId }, {});
|
||||||
}
|
}
|
||||||
@@ -487,14 +489,6 @@ export function addIDPLink(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createUser(
|
|
||||||
provider: IdentityProviderType,
|
|
||||||
info: IDPInformation,
|
|
||||||
) {
|
|
||||||
const userData = PROVIDER_MAPPING[provider](info);
|
|
||||||
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
|
||||||
|
Reference in New Issue
Block a user