mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-04 21:04:37 +08:00
rename signed_up_at_millis JWT claim to signed_up_at
This commit is contained in:
parent
9cbbafeb65
commit
cf53313ff4
@ -314,7 +314,7 @@ export async function generateAccessTokenFromRefreshTokenIfValid(options: Refres
|
||||
email: user.primary_email,
|
||||
email_verified: user.primary_email_verified,
|
||||
selected_team_id: user.selected_team_id,
|
||||
signed_up_at_millis: user.signed_up_at_millis,
|
||||
signed_up_at: Math.floor(user.signed_up_at_millis / 1000),
|
||||
is_anonymous: user.is_anonymous,
|
||||
is_restricted: user.is_restricted,
|
||||
restricted_reason: user.restricted_reason,
|
||||
|
||||
@ -299,7 +299,7 @@ export namespace Auth {
|
||||
"iss": expectedIssuer,
|
||||
"branch_id": "main",
|
||||
"refresh_token_id": expect.any(String),
|
||||
"signed_up_at_millis": expect.any(Number),
|
||||
"signed_up_at": expect.any(Number),
|
||||
"requires_totp_mfa": expect.any(Boolean),
|
||||
"aud": backendContext.value.projectKeys === "no-project" ? expect.any(String) : backendContext.value.projectKeys.projectId,
|
||||
"sub": expect.any(String),
|
||||
|
||||
@ -366,8 +366,8 @@ describe("access token refresh on user property changes", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("signed_up_at_millis claim", () => {
|
||||
it("should include signed_up_at_millis and keep it stable across token refreshes", async ({ expect }) => {
|
||||
describe("signed_up_at claim", () => {
|
||||
it("should include signed_up_at and keep it stable across token refreshes", async ({ expect }) => {
|
||||
const { clientApp } = await createApp({
|
||||
config: {
|
||||
credentialEnabled: true,
|
||||
@ -385,7 +385,8 @@ describe("access token refresh on user property changes", () => {
|
||||
expect(initialToken).toBeDefined();
|
||||
|
||||
const initialPayload = decodeAccessToken(initialToken!);
|
||||
expect(initialPayload.signed_up_at_millis).toBe(user.signedUpAt.getTime());
|
||||
const signedUpAtSeconds = Math.floor(user.signedUpAt.getTime() / 1000);
|
||||
expect(initialPayload.signed_up_at).toBe(signedUpAtSeconds);
|
||||
|
||||
await user.setDisplayName("Updated display name");
|
||||
|
||||
@ -393,7 +394,7 @@ describe("access token refresh on user property changes", () => {
|
||||
expect(refreshedToken).toBeDefined();
|
||||
|
||||
const refreshedPayload = decodeAccessToken(refreshedToken!);
|
||||
expect(refreshedPayload.signed_up_at_millis).toBe(user.signedUpAt.getTime());
|
||||
expect(refreshedPayload.signed_up_at).toBe(signedUpAtSeconds);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -154,5 +154,5 @@ A: In `packages/template/src/components-page/stack-handler-client.tsx`, parse ha
|
||||
Q: What is the current `app.urls` contract after deprecating runtime URL mutation?
|
||||
A: `app.urls` is now static (`getUrls(...)` only) and no longer injects runtime `after_auth_return_to` / `stack_cross_domain_*` params from `window.location`. For navigation flows, examples and consumers should use `redirectToXyz()` methods instead (for example `redirectToSignIn()` / `redirectToSignOut()`), while tests for hosted flows should assert dynamic params on actual redirect methods, not on `app.urls`.
|
||||
|
||||
Q: How should new JWT claims be rolled out without breaking old access tokens?
|
||||
A: Add the claim to token generation in `apps/backend/src/lib/tokens.tsx`, but keep the decode schema backward-compatible by adding the field as optional in `packages/stack-shared/src/schema-fields.ts` with a `// TODO next-release` to later switch it to `.defined()` after all deployments issue the new claim.
|
||||
Q: How should user signup time be exposed in JWT claims before production rollout?
|
||||
A: Use `signed_up_at` (OIDC-style naming) in access tokens and encode it as Unix seconds in `apps/backend/src/lib/tokens.tsx` (`Math.floor(user.signed_up_at_millis / 1000)`). Since this is pre-prod, the payload schema can require `signed_up_at` directly without a backward-compat optional shim.
|
||||
|
||||
@ -47,7 +47,7 @@ Stack Auth JWTs contain standardized headers and claims that power authenticatio
|
||||
- **`email`**: The user's primary email address (nullable)
|
||||
- **`email_verified`**: Whether the user's email has been verified
|
||||
- **`selected_team_id`**: The currently selected team ID (nullable)
|
||||
- **`signed_up_at_millis`**: When this user signed up (Unix timestamp in milliseconds)
|
||||
- **`signed_up_at`**: When this user signed up (Unix timestamp in seconds)
|
||||
- **`is_anonymous`**: Whether this is an anonymous user session
|
||||
- **`is_restricted`**: Whether the user is restricted (e.g., unverified email, anonymous, or restricted by an administrator)
|
||||
- **`restricted_reason`**: Why the user is restricted (nullable). The `type` field is `anonymous`, `email_not_verified`, or `restricted_by_administrator`
|
||||
@ -72,7 +72,7 @@ Here's what a typical Stack Auth JWT payload looks like:
|
||||
"email": "john@example.com",
|
||||
"email_verified": true,
|
||||
"selected_team_id": "team_789",
|
||||
"signed_up_at_millis": 1735000000000,
|
||||
"signed_up_at": 1735000000,
|
||||
"is_anonymous": false,
|
||||
"is_restricted": false,
|
||||
"restricted_reason": null,
|
||||
|
||||
@ -789,8 +789,7 @@ export const accessTokenPayloadSchema = yupObject({
|
||||
email: yupString().defined().nullable(),
|
||||
email_verified: yupBoolean().defined(),
|
||||
selected_team_id: yupString().defined().nullable(),
|
||||
// TODO next-release: make this .defined() once all deployments generate signed_up_at_millis in access tokens
|
||||
signed_up_at_millis: signedUpAtMillisSchema.optional(),
|
||||
signed_up_at: yupNumber().defined(),
|
||||
is_anonymous: yupBoolean().defined(),
|
||||
is_restricted: yupBoolean().defined(),
|
||||
restricted_reason: restrictedReasonSchema.defined().nullable(),
|
||||
|
||||
Loading…
Reference in New Issue
Block a user