Skip to content

Commit

Permalink
Merge branch 'feat/webauthn/base' into feat/webauthn/basic-methods
Browse files Browse the repository at this point in the history
  • Loading branch information
niftyvictor committed Oct 16, 2024
2 parents f102128 + 41ea347 commit de9fb88
Show file tree
Hide file tree
Showing 68 changed files with 654 additions and 386 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,16 @@ import { getAppDirRequestHandler } from "supertokens-node/nextjs";
const handleCall = getAppDirRequestHandler();
```

## [20.1.5] - 2024-10-09

- Fixes an issue where users were not able to reset their password if a user with the same email address was created before account linking was enabled.
- Fixes and re-works some security checks connected to password reset.

## [20.1.4] - 2024-10-07

- Fixes an issue where revoking sessions for a specific tenant didn't work well
- Fixes an issue where the automatic session revocation after linking didn't work across all tenants

## [20.1.3] - 2024-09-30

- Replaces `psl` with `tldts` to avoid `punycode` deprecation warning.
Expand Down
2 changes: 1 addition & 1 deletion docs/classes/framework.BaseRequest.html

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/classes/framework.BaseResponse.html

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions docs/classes/framework_custom.CollectingResponse.html

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/classes/framework_custom.PreParsedRequest.html

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/classes/index.RecipeUserId.html

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/classes/index.User.html

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/classes/index.default.html

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/classes/ingredients_emaildelivery.default.html

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/classes/ingredients_smsdelivery.default.html

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions docs/classes/recipe_accountlinking.default.html

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/classes/recipe_dashboard.default.html

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions docs/classes/recipe_emailpassword.default.html

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/classes/recipe_emailverification.default.html

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/classes/recipe_jwt.default.html

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/classes/recipe_multifactorauth.default.html

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/classes/recipe_multitenancy.default.html

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/classes/recipe_openid.default.html

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions docs/classes/recipe_passwordless.default.html

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions docs/classes/recipe_session.default.html

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/classes/recipe_thirdparty.default.html

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/classes/recipe_totp.default.html

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/classes/recipe_usermetadata.default.html

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/classes/recipe_userroles.default.html

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/interfaces/framework_awsLambda.SessionEvent.html

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/interfaces/framework_awsLambda.SessionEventV2.html

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions docs/interfaces/framework_express.SessionRequest.html

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions docs/interfaces/framework_hapi.SessionRequest.html

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions docs/interfaces/framework_koa.SessionContext.html

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions docs/interfaces/framework_loopback.SessionContext.html

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/interfaces/recipe_session.SessionContainer.html

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/interfaces/recipe_session.VerifySessionOptions.html

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/modules/framework.html

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/modules/framework_awsLambda.html

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/modules/framework_custom.html

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/modules/framework_express.html

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/modules/framework_fastify.html

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/modules/framework_hapi.html

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/modules/framework_koa.html

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/modules/framework_loopback.html

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/modules/index.html

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/modules/recipe_accountlinking.html

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/modules/recipe_dashboard.html

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions docs/modules/recipe_emailpassword.html

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/modules/recipe_emailverification.html

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/modules/recipe_jwt.html

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/modules/recipe_multifactorauth.html

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/modules/recipe_multitenancy.html

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/modules/recipe_openid.html

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/modules/recipe_passwordless.html

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions docs/modules/recipe_session.html

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/modules/recipe_thirdparty.html

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/modules/recipe_totp.html

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions docs/modules/recipe_usermetadata.html

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/modules/recipe_userroles.html

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion lib/build/recipe/accountlinking/recipe.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,12 @@ export default class Recipe extends RecipeModule {
recipeUserId: RecipeUserId;
userContext: UserContext;
}) => Promise<void>;
private shouldBecomePrimaryUser;
shouldBecomePrimaryUser(
user: User,
tenantId: string,
session: SessionContainerInterface | undefined,
userContext: UserContext
): Promise<boolean>;
tryLinkingByAccountInfoOrCreatePrimaryUser({
inputUser,
session,
Expand Down
351 changes: 204 additions & 147 deletions lib/build/recipe/emailpassword/api/implementation.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion lib/build/recipe/emailverification/recipe.js
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ class Recipe extends recipeModule_1.default {
await session_1.default.revokeAllSessionsForUser(
input.recipeUserIdWhoseEmailGotVerified.getAsString(),
false,
input.session.getTenantId(),
undefined,
input.userContext
);
// create a new session and return that..
Expand Down
10 changes: 9 additions & 1 deletion lib/build/recipe/session/cookieAndHeaders.js
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,15 @@ function parseCookieStringFromRequestHeaderAllowingDuplicates(cookieString) {
const [name, value] = cookiePair
.trim()
.split("=")
.map((part) => decodeURIComponent(part));
.map((part) => {
try {
return decodeURIComponent(part);
} catch (e) {
// this is there in case the cookie value is not encoded. This can happe
// if the user has set their own cookie in a different format.
return part;
}
});
if (cookies.hasOwnProperty(name)) {
cookies[name].push(value);
} else {
Expand Down
1 change: 1 addition & 0 deletions lib/build/recipe/session/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ class SessionWrapper {
return recipe_1.default.getInstanceOrThrowError().recipeInterfaceImpl.revokeAllSessionsForUser({
userId,
tenantId: tenantId === undefined ? constants_1.DEFAULT_TENANT_ID : tenantId,
revokeAcrossAllTenants: tenantId === undefined,
revokeSessionsForLinkedAccounts,
userContext: utils_2.getUserContext(userContext),
});
Expand Down
2 changes: 1 addition & 1 deletion lib/ts/recipe/accountlinking/recipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -740,7 +740,7 @@ export default class Recipe extends RecipeModule {
}
};

private async shouldBecomePrimaryUser(
public async shouldBecomePrimaryUser(
user: User,
tenantId: string,
session: SessionContainerInterface | undefined,
Expand Down
371 changes: 218 additions & 153 deletions lib/ts/recipe/emailpassword/api/implementation.ts

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion lib/ts/recipe/emailverification/recipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ export default class Recipe extends RecipeModule {
await Session.revokeAllSessionsForUser(
input.recipeUserIdWhoseEmailGotVerified.getAsString(),
false,
input.session.getTenantId(),
undefined,
input.userContext
);

Expand Down
10 changes: 9 additions & 1 deletion lib/ts/recipe/session/cookieAndHeaders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,15 @@ function parseCookieStringFromRequestHeaderAllowingDuplicates(cookieString: stri
const [name, value] = cookiePair
.trim()
.split("=")
.map((part) => decodeURIComponent(part));
.map((part) => {
try {
return decodeURIComponent(part);
} catch (e) {
// this is there in case the cookie value is not encoded. This can happe
// if the user has set their own cookie in a different format.
return part;
}
});

if (cookies.hasOwnProperty(name)) {
cookies[name].push(value);
Expand Down
1 change: 1 addition & 0 deletions lib/ts/recipe/session/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,7 @@ export default class SessionWrapper {
return Recipe.getInstanceOrThrowError().recipeInterfaceImpl.revokeAllSessionsForUser({
userId,
tenantId: tenantId === undefined ? DEFAULT_TENANT_ID : tenantId,
revokeAcrossAllTenants: tenantId === undefined,
revokeSessionsForLinkedAccounts,
userContext: getUserContext(userContext),
});
Expand Down
58 changes: 58 additions & 0 deletions test/session.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,64 @@ describe(`session: ${printPath("[test/session.test.js]")}`, function () {
assert(cookies.refreshTokenExpiry === new Date(0).toUTCString());
});

it("test that custom cookie format does nto throw an error during cookie parsing", async function () {
const connectionURI = await startST();
SuperTokens.init({
supertokens: {
connectionURI,
},
appInfo: {
apiDomain: "api.supertokens.io",
appName: "SuperTokens",
websiteDomain: "supertokens.io",
},
recipeList: [Session.init({ getTokenTransferMethod: () => "cookie", antiCsrf: "VIA_TOKEN" })],
});
const app = express();
app.use(middleware());

app.post("/create", async (req, res) => {
await Session.createNewSession(req, res, "public", SuperTokens.convertToRecipeUserId("testuserid"), {}, {});
res.status(200).send("");
});

app.use(errorHandler());
let res = extractInfoFromResponse(
await new Promise((resolve) =>
request(app)
.post("/create")
.expect(200)
.end((err, res) => {
if (err) {
resolve(undefined);
} else {
resolve(res);
}
})
)
);

let res2 = await new Promise((resolve) =>
request(app)
.post("/auth/session/refresh")
.set("Cookie", ["sAccessToken=" + res.accessToken + ";custom=" + JSON.stringify({ a: "b%b" })])
.set("anti-csrf", res.antiCsrf)
.end((err, res) => {
if (err) {
resolve(undefined);
} else {
resolve(res);
}
})
);
let cookies = extractInfoFromResponse(res2);
assert(res2.status === 401);
assert(cookies.accessToken === "");
assert(cookies.accessTokenExpiry === new Date(0).toUTCString());
assert(cookies.refreshToken === "");
assert(cookies.refreshTokenExpiry === new Date(0).toUTCString());
});

it("test that session tokens are cleared if refresh token api is called without the refresh token but with an expired access token", async function () {
const connectionURI = await startST({ coreConfig: { access_token_validity: 1 } });
SuperTokens.init({
Expand Down
60 changes: 55 additions & 5 deletions test/test-server/src/testFunctionMapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,44 @@ export function getFunc(evalStr: string): (...args: any[]) => any {
}

if (evalStr.startsWith("accountlinking.init.shouldDoAutomaticAccountLinking")) {
if (evalStr.includes("onlyLinkIfNewUserVerified")) {
return async (newUserAccount, existingUser, session, tenantId, userContext) => {
if (userContext.DO_NOT_LINK) {
return { shouldAutomaticallyLink: false };
}
// if the user account uses third party, and if it is about to be linked to an existing user
if (newUserAccount.thirdParty !== undefined && existingUser !== undefined) {
// The main idea here is that we want to do account linking only if we know that the
// email is already verified for the newUserAccount. If we know that that's not the case,
// then we do not link it. It will result in a new user being created, and then an email
// verification email being sent out to them. Once they verify it, we will try linking again,
// but this time, we know that the email is verified, so it will succeed.
if (userContext.isVerified) {
// This signal comes in from the signInUp function override - from the third party provider.
return {
shouldAutomaticallyLink: true,
shouldRequireVerification: true,
};
}
// if (newUserAccount.recipeUserId !== undefined) {
// let isEmailVerified = await EmailVerification.isEmailVerified(newUserAccount.recipeUserId, undefined, userContext);
// if (isEmailVerified) {
// return {
// shouldAutomaticallyLink: true,
// shouldRequireVerification: true,
// }
// }
// }
return {
shouldAutomaticallyLink: false,
};
}
return {
shouldAutomaticallyLink: true,
shouldRequireVerification: true,
};
};
}
return async (i, l, o, u, a) => {
// Handle specific user context cases
if (evalStr.includes("()=>({shouldAutomaticallyLink:!0,shouldRequireVerification:!1})")) {
Expand Down Expand Up @@ -416,6 +454,18 @@ export function getFunc(evalStr: string): (...args: any[]) => any {
};
}

if (evalStr.startsWith("thirdparty.init.override.functions")) {
if (evalStr.includes("setIsVerifiedInSignInUp")) {
return (originalImplementation) => ({
...originalImplementation,
signInUpPOST: async function (input) {
input.userContext.isVerified = input.isVerified; // this information comes from the third party provider
return await originalImplementation.signInUp(input);
},
});
}
}

if (evalStr.startsWith("thirdparty.init.override.apis")) {
return (n) => ({
...n,
Expand Down Expand Up @@ -448,10 +498,10 @@ export function getFunc(evalStr: string): (...args: any[]) => any {
if (evalStr.includes("custom-no-ev")) {
return (e) => ({
...e,
exchangeAuthCodeForOAuthTokens: () => ({}),
getUserInfo: () => ({
thirdPartyUserId: "user",
email: { id: "email@test.com", isVerified: false },
exchangeAuthCodeForOAuthTokens: ({ redirectURIInfo: e }) => e,
getUserInfo: ({ oAuthTokens: e }) => ({
thirdPartyUserId: e.userId ?? "user",
email: { id: e.email ?? "email@test.com", isVerified: false },
rawUserInfoFromProvider: {},
}),
});
Expand Down Expand Up @@ -492,5 +542,5 @@ export function getFunc(evalStr: string): (...args: any[]) => any {
}
}

throw new Error("Unknown eval string");
throw new Error("Unknown eval string: " + evalStr);
}
7 changes: 6 additions & 1 deletion test/userContext.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -549,7 +549,12 @@ describe(`userContext: ${printPath("[test/userContext.test.js]")}`, function ()
{
fileName: "lib/ts/nextjs.ts",
shouldNotContain: ["userContext: Record<String, any>", "userContext?: UserContext"],
canContain: [{ text: "userContext?: Record<String, any>", count: 2 }],
canContain: [{ text: "userContext?: Record<String, any>", count: 1 }],
},
{
fileName: "lib/ts/customFramework.ts",
shouldNotContain: ["userContext: Record<String, any>", "userContext?: UserContext"],
canContain: [{ text: "userContext?: Record<String, any>", count: 1 }],
},
];

Expand Down

0 comments on commit de9fb88

Please sign in to comment.