Skip to content

Commit 175a3a3

Browse files
coolbuebniftyvictorporcellus
authored
feat: Dashboard WebAuthn support (#984)
* added support for webauthn user editing * bumped version * chore: update version and rebuild --------- Co-authored-by: Victor Bojica <[email protected]> Co-authored-by: Mihaly Lengyel <[email protected]>
1 parent d547593 commit 175a3a3

File tree

17 files changed

+122
-19
lines changed

17 files changed

+122
-19
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html)
77

8+
## [22.0.1] - 2025-03-26
9+
10+
- Added Dashboard support for WebAuthn
11+
812
## [22.0.0] - 2025-03-19
913

1014
### Breaking changes

lib/build/recipe/dashboard/api/multitenancy/utils.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ function factorIdToRecipe(factorId) {
6666
"link-email": "Passwordless",
6767
"link-phone": "Passwordless",
6868
totp: "Totp",
69+
webauthn: "WebAuthn",
6970
};
7071
return factorIdToRecipe[factorId];
7172
}

lib/build/recipe/dashboard/api/userdetails/userPut.js

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@ exports.userPut = void 0;
99
const error_1 = __importDefault(require("../../../../error"));
1010
const recipe_1 = __importDefault(require("../../../emailpassword/recipe"));
1111
const recipe_2 = __importDefault(require("../../../passwordless/recipe"));
12+
const recipe_3 = __importDefault(require("../../../webauthn/recipe"));
1213
const emailpassword_1 = __importDefault(require("../../../emailpassword"));
1314
const passwordless_1 = __importDefault(require("../../../passwordless"));
15+
const webauthn_1 = __importDefault(require("../../../webauthn"));
1416
const utils_1 = require("../../utils");
15-
const recipe_3 = __importDefault(require("../../../usermetadata/recipe"));
17+
const recipe_4 = __importDefault(require("../../../usermetadata/recipe"));
1618
const usermetadata_1 = __importDefault(require("../../../usermetadata"));
1719
const constants_1 = require("../../../emailpassword/constants");
1820
const utils_2 = require("../../../passwordless/utils");
@@ -99,6 +101,33 @@ const updateEmailForRecipeId = async (recipeId, recipeUserId, email, tenantId, u
99101
status: "OK",
100102
};
101103
}
104+
if (recipeId === "webauthn") {
105+
let validationError = await recipe_3.default
106+
.getInstanceOrThrowError()
107+
.config.validateEmailAddress(email, tenantId, userContext);
108+
if (validationError !== undefined) {
109+
return {
110+
status: "INVALID_EMAIL_ERROR",
111+
error: validationError,
112+
};
113+
}
114+
const emailUpdateResponse = await webauthn_1.default.updateUserEmail({
115+
email,
116+
recipeUserId: recipeUserId.getAsString(),
117+
tenantId,
118+
userContext,
119+
});
120+
if (emailUpdateResponse.status === "EMAIL_ALREADY_EXISTS_ERROR") {
121+
return {
122+
status: "EMAIL_ALREADY_EXISTS_ERROR",
123+
};
124+
} else if (emailUpdateResponse.status === "UNKNOWN_USER_ID_ERROR") {
125+
throw new Error("Should never come here");
126+
}
127+
return {
128+
status: "OK",
129+
};
130+
}
102131
/**
103132
* If it comes here then the user is a third party user in which case the UI should not have allowed this
104133
*/
@@ -211,7 +240,7 @@ const userPut = async (_, tenantId, options, userContext) => {
211240
if (firstName.trim() !== "" || lastName.trim() !== "") {
212241
let isRecipeInitialised = false;
213242
try {
214-
recipe_3.default.getInstanceOrThrowError();
243+
recipe_4.default.getInstanceOrThrowError();
215244
isRecipeInitialised = true;
216245
} catch (_) {
217246
// no op

lib/build/recipe/dashboard/types.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export declare type APIFunction = (
5151
options: APIOptions,
5252
userContext: UserContext
5353
) => Promise<any>;
54-
export declare type RecipeIdForUser = "emailpassword" | "thirdparty" | "passwordless";
54+
export declare type RecipeIdForUser = "emailpassword" | "thirdparty" | "passwordless" | "webauthn";
5555
export declare type AuthMode = "api-key" | "email-password";
5656
export declare type UserWithFirstAndLastName = User & {
5757
firstName?: string;

lib/build/recipe/dashboard/utils.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export declare function getUserForRecipeId(
1212
userContext: UserContext
1313
): Promise<{
1414
user: UserWithFirstAndLastName | undefined;
15-
recipe: "emailpassword" | "thirdparty" | "passwordless" | undefined;
15+
recipe: "emailpassword" | "thirdparty" | "passwordless" | "webauthn" | undefined;
1616
}>;
1717
export declare function validateApiKey(input: {
1818
req: BaseRequest;

lib/build/recipe/dashboard/utils.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ const recipe_1 = __importDefault(require("../accountlinking/recipe"));
2626
const recipe_2 = __importDefault(require("../emailpassword/recipe"));
2727
const recipe_3 = __importDefault(require("../thirdparty/recipe"));
2828
const recipe_4 = __importDefault(require("../passwordless/recipe"));
29+
const recipe_5 = __importDefault(require("../webauthn/recipe"));
2930
const logger_1 = require("../../logger");
3031
function validateAndNormaliseUserInput(config) {
3132
let override = Object.assign(
@@ -57,7 +58,12 @@ function sendUnauthorisedAccess(res) {
5758
}
5859
exports.sendUnauthorisedAccess = sendUnauthorisedAccess;
5960
function isValidRecipeId(recipeId) {
60-
return recipeId === "emailpassword" || recipeId === "thirdparty" || recipeId === "passwordless";
61+
return (
62+
recipeId === "emailpassword" ||
63+
recipeId === "thirdparty" ||
64+
recipeId === "passwordless" ||
65+
recipeId === "webauthn"
66+
);
6167
}
6268
exports.isValidRecipeId = isValidRecipeId;
6369
async function getUserForRecipeId(recipeUserId, recipeId, userContext) {
@@ -115,6 +121,13 @@ async function _getUserForRecipeId(recipeUserId, recipeId, userContext) {
115121
} catch (e) {
116122
// No - op
117123
}
124+
} else if (recipeId === recipe_5.default.RECIPE_ID) {
125+
try {
126+
recipe_5.default.getInstanceOrThrowError();
127+
recipe = "webauthn";
128+
} catch (e) {
129+
// No - op
130+
}
118131
}
119132
return {
120133
user,

lib/build/recipe/multitenancy/api/implementation.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ function getAPIInterface() {
7676
enabled: validFirstFactors.includes("thirdparty"),
7777
providers: finalProviderList,
7878
},
79+
webauthn: {
80+
enabled: validFirstFactors.includes("webauthn"),
81+
},
7982
passwordless: {
8083
enabled:
8184
validFirstFactors.includes("otp-email") ||

lib/build/recipe/webauthn/index.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,9 +223,9 @@ export default class Wrapper {
223223
status:
224224
| "OK"
225225
| "UNKNOWN_USER_ID_ERROR"
226-
| "INVALID_CREDENTIALS_ERROR"
227226
| "INVALID_OPTIONS_ERROR"
228227
| "OPTIONS_NOT_FOUND_ERROR"
228+
| "INVALID_CREDENTIALS_ERROR"
229229
| "INVALID_AUTHENTICATOR_ERROR"
230230
| "CREDENTIAL_NOT_FOUND_ERROR";
231231
}>;

lib/build/version.d.ts

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/build/version.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)