supertokens-node
Version:
NodeJS driver for SuperTokens core
300 lines (299 loc) • 13.6 kB
JavaScript
;
/* Copyright (c) 2025, VRAI Labs and/or its affiliates. All rights reserved.
*
* This software is licensed under the Apache License, Version 2.0 (the
* "License") as published by the Apache Software Foundation.
*
* You may not use this file except in compliance with the License. You may
* obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = getRecipeInterface;
const recipeUserId_1 = __importDefault(require("../../recipeUserId"));
const user_1 = require("../../user");
const authUtils_1 = require("../../authUtils");
function getRecipeInterface(stInstance, querier, getWebauthnConfig) {
return {
registerOptions: async function (_a) {
var _b, _c;
var { relyingPartyId, relyingPartyName, origin, timeout, attestation = "none", tenantId, userContext, supportedAlgorithmIds, userVerification, userPresence, residentKey } = _a, rest = __rest(_a, ["relyingPartyId", "relyingPartyName", "origin", "timeout", "attestation", "tenantId", "userContext", "supportedAlgorithmIds", "userVerification", "userPresence", "residentKey"]);
const emailInput = "email" in rest ? rest.email : undefined;
const recoverAccountTokenInput = "recoverAccountToken" in rest ? rest.recoverAccountToken : undefined;
let email;
if (emailInput !== undefined) {
email = emailInput;
}
else if (recoverAccountTokenInput !== undefined) {
const result = await this.getUserFromRecoverAccountToken({
token: recoverAccountTokenInput,
tenantId,
userContext,
});
if (result.status !== "OK") {
return result;
}
const user = result.user;
// if the recipeUserId is not present, it means that the user does not have a webauthn login method and we should just use the user id
// this will make account recovery act as a sign up
const userId = ((_b = result.recipeUserId) === null || _b === void 0 ? void 0 : _b.getAsString()) || user.id;
email = (_c = user.loginMethods.find((lm) => lm.recipeUserId.getAsString() === userId)) === null || _c === void 0 ? void 0 : _c.email;
}
if (!email) {
return {
status: "INVALID_EMAIL_ERROR",
err: "The email is missing",
};
}
const err = await getWebauthnConfig().validateEmailAddress(email, tenantId, userContext);
if (err) {
return {
status: "INVALID_EMAIL_ERROR",
err,
};
}
let displayName;
if ("displayName" in rest && rest.displayName !== undefined) {
displayName = rest.displayName;
}
else {
displayName = email;
}
return await querier.sendPostRequest({
path: "/<tenantId>/recipe/webauthn/options/register",
params: {
tenantId: tenantId,
},
}, {
email,
displayName,
relyingPartyName,
relyingPartyId,
origin,
timeout,
attestation,
supportedAlgorithmIds,
userVerification,
userPresence,
residentKey,
}, userContext);
},
signInOptions: async function ({ relyingPartyId, relyingPartyName, origin, timeout, userVerification, userPresence, tenantId, userContext, }) {
return await querier.sendPostRequest({
path: "/<tenantId>/recipe/webauthn/options/signin",
params: {
tenantId: tenantId,
},
}, {
userVerification,
userPresence,
relyingPartyId,
relyingPartyName,
origin,
timeout,
}, userContext);
},
signUp: async function ({ webauthnGeneratedOptionsId, credential, tenantId, session, shouldTryLinkingWithSessionUser, userContext }) {
const response = await this.createNewRecipeUser({
credential,
webauthnGeneratedOptionsId,
tenantId,
userContext,
});
if (response.status !== "OK") {
return response;
}
const linkResult = await authUtils_1.AuthUtils.linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo({
stInstance,
tenantId,
inputUser: response.user,
recipeUserId: response.recipeUserId,
session,
shouldTryLinkingWithSessionUser,
userContext,
});
if (linkResult.status != "OK") {
return linkResult;
}
return {
status: "OK",
user: linkResult.user,
recipeUserId: response.recipeUserId,
};
},
signIn: async function ({ credential, webauthnGeneratedOptionsId, tenantId, session, shouldTryLinkingWithSessionUser, userContext }) {
const response = await this.verifyCredentials({
credential,
webauthnGeneratedOptionsId,
tenantId,
userContext,
});
if (response.status !== "OK") {
return response;
}
const loginMethod = response.user.loginMethods.find((lm) => lm.recipeUserId.getAsString() === response.recipeUserId.getAsString());
if (!loginMethod.verified) {
await stInstance
.getRecipeInstanceOrThrow("accountlinking")
.verifyEmailForRecipeUserIfLinkedAccountsAreVerified({
user: response.user,
recipeUserId: response.recipeUserId,
userContext,
});
// We do this so that we get the updated user (in case the above
// function updated the verification status) and can return that
response.user = (await stInstance
.getRecipeInstanceOrThrow("accountlinking")
.recipeInterfaceImpl.getUser({ userId: response.recipeUserId.getAsString(), userContext }));
}
const linkResult = await authUtils_1.AuthUtils.linkToSessionIfRequiredElseCreatePrimaryUserIdOrLinkByAccountInfo({
stInstance,
tenantId,
inputUser: response.user,
recipeUserId: response.recipeUserId,
session,
shouldTryLinkingWithSessionUser,
userContext,
});
if (linkResult.status === "LINKING_TO_SESSION_USER_FAILED") {
return linkResult;
}
response.user = linkResult.user;
return response;
},
verifyCredentials: async function ({ credential, webauthnGeneratedOptionsId, tenantId, userContext }) {
const response = await querier.sendPostRequest({
path: "/<tenantId>/recipe/webauthn/signin",
params: {
tenantId: tenantId,
},
}, {
credential,
webauthnGeneratedOptionsId,
}, userContext);
if (response.status === "OK") {
return {
status: "OK",
user: new user_1.User(response.user),
recipeUserId: new recipeUserId_1.default(response.recipeUserId),
};
}
return response;
},
createNewRecipeUser: async function (input) {
const resp = await querier.sendPostRequest({
path: "/<tenantId>/recipe/webauthn/signup",
params: {
tenantId: input.tenantId,
},
}, {
webauthnGeneratedOptionsId: input.webauthnGeneratedOptionsId,
credential: input.credential,
}, input.userContext);
if (resp.status === "OK") {
return {
status: "OK",
user: new user_1.User(resp.user),
recipeUserId: new recipeUserId_1.default(resp.recipeUserId),
};
}
return resp;
},
generateRecoverAccountToken: async function ({ userId, email, tenantId, userContext }) {
return await querier.sendPostRequest({
path: "/<tenantId>/recipe/webauthn/user/recover/token",
params: {
tenantId: tenantId,
},
}, {
userId,
email,
}, userContext);
},
consumeRecoverAccountToken: async function ({ token, tenantId, userContext }) {
return await querier.sendPostRequest({
path: "/<tenantId>/recipe/webauthn/user/recover/token/consume",
params: {
tenantId: tenantId,
},
}, {
token,
}, userContext);
},
registerCredential: async function ({ webauthnGeneratedOptionsId, credential, userContext, recipeUserId }) {
return await querier.sendPostRequest("/recipe/webauthn/user/credential/register", {
recipeUserId,
webauthnGeneratedOptionsId,
credential,
}, userContext);
},
getUserFromRecoverAccountToken: async function ({ token, tenantId, userContext }) {
const resp = await querier.sendGetRequest({
path: "/<tenantId>/recipe/webauthn/user/recover",
params: {
tenantId: tenantId,
},
}, { token }, userContext);
if (resp.status === "OK") {
return Object.assign(Object.assign({}, resp), { user: new user_1.User(resp.user), recipeUserId: resp.recipeUserId ? new recipeUserId_1.default(resp.recipeUserId) : undefined });
}
return resp;
},
removeCredential: async function ({ webauthnCredentialId, recipeUserId, userContext }) {
return await querier.sendDeleteRequest("/recipe/webauthn/user/credential/remove", undefined, { recipeUserId, webauthnCredentialId }, userContext);
},
getCredential: async function ({ webauthnCredentialId, recipeUserId, userContext }) {
const resp = await querier.sendGetRequest("/recipe/webauthn/user/credential", { webauthnCredentialId, recipeUserId }, userContext);
if (resp.status === "OK") {
return Object.assign(Object.assign({}, resp), { recipeUserId: new recipeUserId_1.default(resp.recipeUserId) });
}
return resp;
},
listCredentials: async function ({ recipeUserId, userContext }) {
return await querier.sendGetRequest("/recipe/webauthn/user/credential/list", { recipeUserId }, userContext);
},
removeGeneratedOptions: async function ({ webauthnGeneratedOptionsId, tenantId, userContext }) {
return await querier.sendDeleteRequest({
path: "/<tenantId>/recipe/webauthn/options/remove",
params: {
tenantId: tenantId,
},
}, undefined, { webauthnGeneratedOptionsId }, userContext);
},
getGeneratedOptions: async function ({ webauthnGeneratedOptionsId, tenantId, userContext }) {
return await querier.sendGetRequest({
path: "/<tenantId>/recipe/webauthn/options",
params: {
tenantId: tenantId || "public",
},
}, { webauthnGeneratedOptionsId }, userContext);
},
updateUserEmail: async function ({ email, recipeUserId, tenantId, userContext }) {
return await querier.sendPutRequest({
path: "/<tenantId>/recipe/webauthn/user/email",
params: {
tenantId: tenantId,
},
}, { email, recipeUserId }, {}, userContext);
},
};
}