mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-13 21:01:21 +08:00
Merge remote-tracking branch 'origin/dev' into cl/romantic-mendel-5a2c25
This commit is contained in:
commit
d59cc378e8
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@stackframe/backend",
|
||||
"version": "2.8.105",
|
||||
"version": "2.8.106",
|
||||
"repository": "https://github.com/hexclave/hexclave",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@ -13,7 +13,7 @@ import { InvalidClientError, InvalidScopeError, Request as OAuthRequest, Respons
|
||||
import { KnownError, KnownErrors } from "@stackframe/stack-shared";
|
||||
import { yupMixed, yupNumber, yupObject, yupString } from "@stackframe/stack-shared/dist/schema-fields";
|
||||
import { HexclaveAssertionError, StatusError, captureError } from "@stackframe/stack-shared/dist/utils/errors";
|
||||
import { deindent, extractScopes } from "@stackframe/stack-shared/dist/utils/strings";
|
||||
import { deindent, extractScopes, mergeScopeStrings } from "@stackframe/stack-shared/dist/utils/strings";
|
||||
import { cookies } from "next/headers";
|
||||
import { redirect } from "next/navigation";
|
||||
import { oauthResponseToSmartResponse } from "../../oauth-helpers";
|
||||
@ -224,12 +224,13 @@ const handler = createSmartRouteHandler({
|
||||
});
|
||||
|
||||
const storeTokens = async (oauthAccountId: string) => {
|
||||
const tokenScopes = extractScopes(mergeScopeStrings(providerObj.scope, providerScope ?? ""));
|
||||
if (tokenSet.refreshToken) {
|
||||
await prisma.oAuthToken.create({
|
||||
data: {
|
||||
tenancyId: outerInfo.tenancyId,
|
||||
refreshToken: tokenSet.refreshToken,
|
||||
scopes: extractScopes(providerObj.scope + " " + providerScope),
|
||||
scopes: tokenScopes,
|
||||
oauthAccountId,
|
||||
}
|
||||
});
|
||||
@ -239,7 +240,7 @@ const handler = createSmartRouteHandler({
|
||||
data: {
|
||||
tenancyId: outerInfo.tenancyId,
|
||||
accessToken: tokenSet.accessToken,
|
||||
scopes: extractScopes(providerObj.scope + " " + providerScope),
|
||||
scopes: tokenScopes,
|
||||
expiresAt: tokenSet.accessTokenExpiredAt,
|
||||
oauthAccountId,
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { getOAuthAccessTokenRefreshError, getOAuthAccessTokenRefreshErrorDisposition, isRetryableOAuthUserInfoError } from "./base";
|
||||
import { getOAuthAccessTokenRefreshError, getOAuthAccessTokenRefreshErrorDisposition, isRetryableOAuthUserInfoError, resolveOAuthAccessTokenExpiredAt } from "./base";
|
||||
|
||||
describe("isRetryableOAuthUserInfoError", () => {
|
||||
it("returns true for openid-client timeout errors", () => {
|
||||
@ -101,3 +101,32 @@ describe("getOAuthAccessTokenRefreshError", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveOAuthAccessTokenExpiredAt", () => {
|
||||
it("uses finite provider expires_in values", () => {
|
||||
expect(resolveOAuthAccessTokenExpiredAt({
|
||||
expiresInSeconds: 120,
|
||||
expiresAtSeconds: undefined,
|
||||
defaultExpiresInMillis: null,
|
||||
nowMillis: 1000,
|
||||
})?.toISOString()).toBe("1970-01-01T00:02:01.000Z");
|
||||
});
|
||||
|
||||
it("ignores non-finite provider expires_at values and uses explicit null defaults", () => {
|
||||
expect(resolveOAuthAccessTokenExpiredAt({
|
||||
expiresInSeconds: undefined,
|
||||
expiresAtSeconds: Number.NaN,
|
||||
defaultExpiresInMillis: null,
|
||||
nowMillis: 1000,
|
||||
})).toBeNull();
|
||||
});
|
||||
|
||||
it("ignores non-finite provider expiry values and falls back to one hour", () => {
|
||||
expect(resolveOAuthAccessTokenExpiredAt({
|
||||
expiresInSeconds: Number.NaN,
|
||||
expiresAtSeconds: Number.NaN,
|
||||
defaultExpiresInMillis: undefined,
|
||||
nowMillis: 1000,
|
||||
})?.toISOString()).toBe("1970-01-01T01:00:01.000Z");
|
||||
});
|
||||
});
|
||||
|
||||
@ -219,6 +219,48 @@ export function getOAuthAccessTokenRefreshError(error: unknown, options: {
|
||||
|
||||
type DefaultAccessTokenExpiresInMillis = number | null | ((tokenSet: OIDCTokenSet) => number | null | undefined);
|
||||
|
||||
function getFiniteNumber(value: unknown): number | undefined {
|
||||
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
||||
}
|
||||
|
||||
function dateFromMillis(millis: number, context: string): Date {
|
||||
const date = new Date(millis);
|
||||
if (!Number.isFinite(date.getTime())) {
|
||||
throw new HexclaveAssertionError(`Invalid OAuth access token expiry computed from ${context}`, { millis });
|
||||
}
|
||||
return date;
|
||||
}
|
||||
|
||||
export function resolveOAuthAccessTokenExpiredAt(options: {
|
||||
expiresInSeconds: unknown,
|
||||
expiresAtSeconds: unknown,
|
||||
defaultExpiresInMillis: number | null | undefined,
|
||||
nowMillis: number,
|
||||
}): Date | null {
|
||||
const expiresInSeconds = getFiniteNumber(options.expiresInSeconds);
|
||||
if (expiresInSeconds !== undefined) {
|
||||
return dateFromMillis(options.nowMillis + expiresInSeconds * 1000, "expires_in");
|
||||
}
|
||||
|
||||
const expiresAtSeconds = getFiniteNumber(options.expiresAtSeconds);
|
||||
if (expiresAtSeconds !== undefined) {
|
||||
return dateFromMillis(expiresAtSeconds * 1000, "expires_at");
|
||||
}
|
||||
|
||||
if (options.defaultExpiresInMillis === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (options.defaultExpiresInMillis !== undefined) {
|
||||
if (!Number.isFinite(options.defaultExpiresInMillis)) {
|
||||
throw new HexclaveAssertionError("Invalid default OAuth access token expiry", { defaultExpiresInMillis: options.defaultExpiresInMillis });
|
||||
}
|
||||
return dateFromMillis(options.nowMillis + options.defaultExpiresInMillis, "provider default");
|
||||
}
|
||||
|
||||
return dateFromMillis(options.nowMillis + 3600 * 1000, "generic fallback");
|
||||
}
|
||||
|
||||
function processTokenSet(providerName: string, tokenSet: OIDCTokenSet, defaultAccessTokenExpiresInMillis?: DefaultAccessTokenExpiresInMillis): TokenSet {
|
||||
if (!tokenSet.access_token) {
|
||||
throw new HexclaveAssertionError(`No access token received from ${providerName}.`, { tokenSet, providerName });
|
||||
@ -230,7 +272,14 @@ function processTokenSet(providerName: string, tokenSet: OIDCTokenSet, defaultAc
|
||||
// one-hour fallback and capture telemetry.
|
||||
const defaultExpiresInMillis = typeof defaultAccessTokenExpiresInMillis === "function" ? defaultAccessTokenExpiresInMillis(tokenSet) : defaultAccessTokenExpiresInMillis;
|
||||
|
||||
if (tokenSet.expires_in == null && tokenSet.expires_at == null && defaultExpiresInMillis === undefined) {
|
||||
const hasInvalidProviderExpiry =
|
||||
(tokenSet.expires_in != null && getFiniteNumber(tokenSet.expires_in) === undefined)
|
||||
|| (tokenSet.expires_at != null && getFiniteNumber(tokenSet.expires_at) === undefined);
|
||||
if (hasInvalidProviderExpiry) {
|
||||
captureError("processTokenSet", new HexclaveAssertionError(`Invalid expires_in or expires_at received from OAuth provider ${providerName}. Falling back to provider/default expiry handling`, { tokenSetKeys: Object.keys(tokenSet) }));
|
||||
}
|
||||
|
||||
if (getFiniteNumber(tokenSet.expires_in) === undefined && getFiniteNumber(tokenSet.expires_at) === undefined && defaultExpiresInMillis === undefined) {
|
||||
captureError("processTokenSet", new HexclaveAssertionError(`No expires_in or expires_at received from OAuth provider ${providerName}. Falling back to 1h`, { tokenSetKeys: Object.keys(tokenSet) }));
|
||||
}
|
||||
|
||||
@ -238,13 +287,12 @@ function processTokenSet(providerName: string, tokenSet: OIDCTokenSet, defaultAc
|
||||
idToken: tokenSet.id_token,
|
||||
accessToken: tokenSet.access_token,
|
||||
refreshToken: tokenSet.refresh_token,
|
||||
accessTokenExpiredAt: tokenSet.expires_in != null ?
|
||||
new Date(Date.now() + tokenSet.expires_in * 1000) :
|
||||
tokenSet.expires_at != null ? new Date(tokenSet.expires_at * 1000) :
|
||||
defaultExpiresInMillis === null ? null :
|
||||
defaultExpiresInMillis !== undefined ?
|
||||
new Date(Date.now() + defaultExpiresInMillis) :
|
||||
new Date(Date.now() + 3600 * 1000),
|
||||
accessTokenExpiredAt: resolveOAuthAccessTokenExpiredAt({
|
||||
expiresInSeconds: tokenSet.expires_in,
|
||||
expiresAtSeconds: tokenSet.expires_at,
|
||||
defaultExpiresInMillis,
|
||||
nowMillis: Date.now(),
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@stackframe/dashboard",
|
||||
"version": "2.8.105",
|
||||
"version": "2.8.106",
|
||||
"repository": "https://github.com/hexclave/hexclave",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@stackframe/dev-launchpad",
|
||||
"version": "2.8.105",
|
||||
"version": "2.8.106",
|
||||
"repository": "https://github.com/hexclave/hexclave",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@stackframe/e2e-tests",
|
||||
"version": "2.8.105",
|
||||
"version": "2.8.106",
|
||||
"repository": "https://github.com/hexclave/hexclave",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@stackframe/hosted-components",
|
||||
"private": true,
|
||||
"version": "2.8.105",
|
||||
"version": "2.8.106",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite dev --port ${NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX:-81}09",
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@stackframe/internal-tool",
|
||||
"private": true,
|
||||
"version": "2.8.105",
|
||||
"version": "2.8.106",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "node scripts/pre-dev.mjs && next dev --turbopack --port ${NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX:-81}41",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@stackframe/mcp",
|
||||
"version": "2.8.105",
|
||||
"version": "2.8.106",
|
||||
"repository": "https://github.com/hexclave/hexclave",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@stackframe/mock-oauth-server",
|
||||
"version": "2.8.105",
|
||||
"version": "2.8.106",
|
||||
"repository": "https://github.com/hexclave/hexclave",
|
||||
"private": true,
|
||||
"main": "index.js",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@stackframe/skills",
|
||||
"version": "2.8.105",
|
||||
"version": "2.8.106",
|
||||
"repository": "https://github.com/hexclave/hexclave",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@stackframe/docs-mintlify",
|
||||
"version": "2.8.105",
|
||||
"version": "2.8.106",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "mint dev --port ${NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX:-81}04 --no-open",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@stackframe/stack-docs",
|
||||
"version": "2.8.105",
|
||||
"version": "2.8.106",
|
||||
"repository": "https://github.com/hexclave/stack-auth",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@stackframe/example-cjs-test",
|
||||
"version": "2.8.105",
|
||||
"version": "2.8.106",
|
||||
"repository": "https://github.com/hexclave/hexclave",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@stackframe/convex-example",
|
||||
"version": "2.8.105",
|
||||
"version": "2.8.106",
|
||||
"repository": "https://github.com/hexclave/hexclave",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@stackframe/example-demo-app",
|
||||
"version": "2.8.105",
|
||||
"version": "2.8.106",
|
||||
"repository": "https://github.com/hexclave/hexclave",
|
||||
"description": "",
|
||||
"private": true,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@stackframe/docs-examples",
|
||||
"version": "2.8.105",
|
||||
"version": "2.8.106",
|
||||
"repository": "https://github.com/hexclave/hexclave",
|
||||
"description": "",
|
||||
"private": true,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@stackframe/e-commerce-demo",
|
||||
"version": "2.8.105",
|
||||
"version": "2.8.106",
|
||||
"repository": "https://github.com/hexclave/hexclave",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@stackframe/js-example",
|
||||
"version": "2.8.105",
|
||||
"version": "2.8.106",
|
||||
"repository": "https://github.com/hexclave/hexclave",
|
||||
"private": true,
|
||||
"description": "",
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@stackframe/lovable-react-18-example",
|
||||
"private": true,
|
||||
"version": "2.8.105",
|
||||
"version": "2.8.106",
|
||||
"repository": "https://github.com/hexclave/hexclave",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@stackframe/example-middleware-demo",
|
||||
"version": "2.8.105",
|
||||
"version": "2.8.106",
|
||||
"repository": "https://github.com/hexclave/hexclave",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "react-example",
|
||||
"private": true,
|
||||
"version": "2.8.105",
|
||||
"version": "2.8.106",
|
||||
"repository": "https://github.com/hexclave/hexclave",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@stackframe/example-supabase",
|
||||
"version": "2.8.105",
|
||||
"version": "2.8.106",
|
||||
"repository": "https://github.com/hexclave/hexclave",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@stackframe/example-tanstack-start-demo",
|
||||
"version": "2.8.105",
|
||||
"version": "2.8.106",
|
||||
"repository": "https://github.com/hexclave/hexclave",
|
||||
"description": "TanStack Start demo app for Hexclave",
|
||||
"private": true,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@stackframe/dashboard-ui-components",
|
||||
"version": "2.8.105",
|
||||
"version": "2.8.106",
|
||||
"repository": "https://github.com/hexclave/hexclave",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@stackframe/init-stack",
|
||||
"version": "2.8.105",
|
||||
"version": "2.8.106",
|
||||
"repository": "https://github.com/hexclave/hexclave",
|
||||
"description": "The setup wizard for Hexclave. https://hexclave.com",
|
||||
"main": "dist/index.mjs",
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"//": "THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY, INSTEAD EDIT THE CORRESPONDING FILE IN packages/template (FOR package.json FILES, PLEASE EDIT package-template.json)",
|
||||
"name": "@stackframe/js",
|
||||
"version": "2.8.105",
|
||||
"version": "2.8.106",
|
||||
"repository": "https://github.com/hexclave/hexclave",
|
||||
"sideEffects": false,
|
||||
"main": "./dist/index.js",
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"//": "THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY, INSTEAD EDIT THE CORRESPONDING FILE IN packages/template (FOR package.json FILES, PLEASE EDIT package-template.json)",
|
||||
"name": "@stackframe/react",
|
||||
"version": "2.8.105",
|
||||
"version": "2.8.106",
|
||||
"repository": "https://github.com/hexclave/hexclave",
|
||||
"sideEffects": false,
|
||||
"main": "./dist/index.js",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@stackframe/stack-cli",
|
||||
"version": "2.8.105",
|
||||
"version": "2.8.106",
|
||||
"repository": "https://github.com/hexclave/hexclave",
|
||||
"description": "The CLI for Hexclave. https://hexclave.com",
|
||||
"main": "dist/index.js",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@stackframe/stack-sc",
|
||||
"version": "2.8.105",
|
||||
"version": "2.8.106",
|
||||
"repository": "https://github.com/hexclave/hexclave",
|
||||
"exports": {
|
||||
"./force-react-server": {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@stackframe/stack-shared",
|
||||
"version": "2.8.105",
|
||||
"version": "2.8.106",
|
||||
"repository": "https://github.com/hexclave/hexclave",
|
||||
"scripts": {
|
||||
"build": "rimraf dist && tsdown",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@stackframe/stack-ui",
|
||||
"version": "2.8.105",
|
||||
"version": "2.8.106",
|
||||
"repository": "https://github.com/hexclave/hexclave",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"//": "THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY, INSTEAD EDIT THE CORRESPONDING FILE IN packages/template (FOR package.json FILES, PLEASE EDIT package-template.json)",
|
||||
"name": "@stackframe/stack",
|
||||
"version": "2.8.105",
|
||||
"version": "2.8.106",
|
||||
"repository": "https://github.com/hexclave/hexclave",
|
||||
"sideEffects": false,
|
||||
"main": "./dist/index.js",
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"//": "THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY, INSTEAD EDIT THE CORRESPONDING FILE IN packages/template (FOR package.json FILES, PLEASE EDIT package-template.json)",
|
||||
"name": "@stackframe/tanstack-start",
|
||||
"version": "2.8.105",
|
||||
"version": "2.8.106",
|
||||
"repository": "https://github.com/hexclave/hexclave",
|
||||
"sideEffects": false,
|
||||
"main": "./dist/index.js",
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
|
||||
"//": "NEXT_LINE_PLATFORM template",
|
||||
"private": true,
|
||||
"version": "2.8.105",
|
||||
"version": "2.8.106",
|
||||
"repository": "https://github.com/hexclave/hexclave",
|
||||
"sideEffects": false,
|
||||
"main": "./dist/index.js",
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
"//": "THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY, INSTEAD EDIT THE CORRESPONDING FILE IN packages/template (FOR package.json FILES, PLEASE EDIT package-template.json)",
|
||||
"name": "@stackframe/template",
|
||||
"private": true,
|
||||
"version": "2.8.105",
|
||||
"version": "2.8.106",
|
||||
"repository": "https://github.com/hexclave/hexclave",
|
||||
"sideEffects": false,
|
||||
"main": "./dist/index.js",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@stackframe/swift-sdk",
|
||||
"version": "2.8.105",
|
||||
"version": "2.8.106",
|
||||
"private": true,
|
||||
"description": "Hexclave Swift SDK",
|
||||
"scripts": {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@stackframe/sdk-spec",
|
||||
"version": "2.8.105",
|
||||
"version": "2.8.106",
|
||||
"private": true,
|
||||
"description": "Hexclave SDK specification files",
|
||||
"scripts": {}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user