mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-13 21:01:21 +08:00
Merge dev into update-oauth-docs
This commit is contained in:
commit
59e40551cb
@ -72,9 +72,15 @@ export const GET = createSmartRouteHandler({
|
||||
|
||||
const posts = data.results || [];
|
||||
|
||||
// Filter out posts that have been merged into other posts or are completed
|
||||
const activePosts = posts.filter((post: any) =>
|
||||
!post.mergedToSubmissionId &&
|
||||
post.postStatus?.type !== 'completed'
|
||||
);
|
||||
|
||||
// Check upvote status for each post for the current user using Featurebase email
|
||||
const postsWithUpvoteStatus = await Promise.all(
|
||||
posts.map(async (post: any) => {
|
||||
activePosts.map(async (post: any) => {
|
||||
let userHasUpvoted = false;
|
||||
|
||||
const upvoteResponse = await fetch(`https://do.featurebase.app/v2/posts/upvoters?submissionId=${post.id}`, {
|
||||
|
||||
@ -5,7 +5,7 @@ import { KnownErrors } from '@stackframe/stack-shared';
|
||||
import { yupBoolean, yupNumber, yupObject, yupString } from "@stackframe/stack-shared/dist/schema-fields";
|
||||
import { generateSecureRandomString } from '@stackframe/stack-shared/dist/utils/crypto';
|
||||
import { getEnvVariable } from '@stackframe/stack-shared/dist/utils/env';
|
||||
import { throwErr } from '@stackframe/stack-shared/dist/utils/errors';
|
||||
import { StackAssertionError, throwErr } from '@stackframe/stack-shared/dist/utils/errors';
|
||||
import { getPrivateJwks, getPublicJwkSet, signJWT, verifyJWT } from '@stackframe/stack-shared/dist/utils/jwt';
|
||||
import { Result } from '@stackframe/stack-shared/dist/utils/results';
|
||||
import { traceSpan } from '@stackframe/stack-shared/dist/utils/telemetry';
|
||||
@ -20,8 +20,6 @@ const accessTokenSchema = yupObject({
|
||||
projectId: yupString().defined(),
|
||||
userId: yupString().defined(),
|
||||
branchId: yupString().defined(),
|
||||
// we make it optional to keep backwards compatibility with old tokens for a while
|
||||
// TODO next-release
|
||||
refreshTokenId: yupString().optional(),
|
||||
exp: yupNumber().defined(),
|
||||
isAnonymous: yupBoolean().defined(),
|
||||
@ -98,11 +96,17 @@ export async function decodeAccessToken(accessToken: string, { allowAnonymous }:
|
||||
return Result.error(new KnownErrors.UnparsableAccessToken());
|
||||
}
|
||||
|
||||
const branchId = payload.branch_id ?? payload.branchId;
|
||||
if (branchId !== "main") {
|
||||
// TODO instead, we should check here that the aud is `projectId#branch` instead
|
||||
throw new StackAssertionError("Branch ID !== main not currently supported.");
|
||||
}
|
||||
|
||||
const result = await accessTokenSchema.validate({
|
||||
projectId: aud.split(":")[0],
|
||||
userId: payload.sub,
|
||||
branchId: payload.branchId,
|
||||
refreshTokenId: payload.refreshTokenId,
|
||||
branchId: branchId,
|
||||
refreshTokenId: payload.refresh_token_id ?? payload.refreshTokenId,
|
||||
exp: payload.exp,
|
||||
isAnonymous: payload.role === 'anon',
|
||||
});
|
||||
@ -137,13 +141,13 @@ export async function generateAccessToken(options: {
|
||||
audience: getAudience(options.tenancy.project.id, user.is_anonymous),
|
||||
payload: {
|
||||
sub: options.userId,
|
||||
branchId: options.tenancy.branchId,
|
||||
refreshTokenId: options.refreshTokenId,
|
||||
branch_id: options.tenancy.branchId,
|
||||
refresh_token_id: options.refreshTokenId,
|
||||
role: user.is_anonymous ? 'anon' : 'authenticated',
|
||||
displayName: user.display_name,
|
||||
primaryEmail: user.primary_email,
|
||||
primaryEmailVerified: user.primary_email_verified,
|
||||
selectedTeamId: user.selected_team_id,
|
||||
name: user.display_name,
|
||||
email: user.primary_email,
|
||||
email_verified: user.primary_email_verified,
|
||||
selected_team_id: user.selected_team_id,
|
||||
},
|
||||
expirationTime: getEnvVariable("STACK_ACCESS_TOKEN_EXPIRATION_TIME", "10min"),
|
||||
});
|
||||
|
||||
@ -191,7 +191,7 @@ export namespace Auth {
|
||||
const aud = jose.decodeJwt(accessToken).aud;
|
||||
const jwks = jose.createRemoteJWKSet(
|
||||
new URL(`api/v1/projects/${aud}/.well-known/jwks.json`, STACK_BACKEND_BASE_URL),
|
||||
{ timeoutDuration: 10_000 },
|
||||
{ timeoutDuration: 20_000 },
|
||||
);
|
||||
const expectedIssuer = new URL(`/api/v1/projects/${aud}`, STACK_BACKEND_BASE_URL).toString();
|
||||
const { payload } = await jose.jwtVerify(accessToken, jwks);
|
||||
@ -199,15 +199,15 @@ export namespace Auth {
|
||||
"exp": expect.any(Number),
|
||||
"iat": expect.any(Number),
|
||||
"iss": expectedIssuer,
|
||||
"refreshTokenId": expect.any(String),
|
||||
"branch_id": "main",
|
||||
"refresh_token_id": expect.any(String),
|
||||
"aud": backendContext.value.projectKeys === "no-project" ? expect.any(String) : backendContext.value.projectKeys.projectId,
|
||||
"sub": expect.any(String),
|
||||
"role": "authenticated",
|
||||
"branchId": "main",
|
||||
"displayName": expect.toSatisfy(() => true),
|
||||
"primaryEmail": expect.toSatisfy(() => true),
|
||||
"primaryEmailVerified": expect.any(Boolean),
|
||||
"selectedTeamId": expect.toSatisfy(() => true),
|
||||
"name": expect.toSatisfy(() => true),
|
||||
"email": expect.toSatisfy(() => true),
|
||||
"email_verified": expect.any(Boolean),
|
||||
"selected_team_id": expect.toSatisfy(() => true),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,6 +36,7 @@ const stripFields = [
|
||||
"access_token",
|
||||
"refresh_token",
|
||||
"refreshTokenId",
|
||||
"refresh_token_id",
|
||||
"exp",
|
||||
"iat",
|
||||
"date",
|
||||
|
||||
@ -16,27 +16,27 @@ import { useSidebar } from '../sidebar-context';
|
||||
const API_COLOR = 'rgb(71, 85, 105)'; // Neutral dark gray (good for light mode)
|
||||
const API_COLOR_LIGHT = 'rgb(148, 163, 184)'; // Lighter neutral gray
|
||||
|
||||
// HTTP Method color scheme - matches the HttpMethodBadge colors exactly
|
||||
// HTTP Method color scheme - matches the enhanced-api-page.tsx colors exactly
|
||||
const METHOD_COLORS = {
|
||||
GET: {
|
||||
main: 'rgb(22, 101, 52)', // green-800 (matches badge text color)
|
||||
light: 'rgb(134, 239, 172)', // green-300 (matches dark mode badge text)
|
||||
},
|
||||
POST: {
|
||||
main: 'rgb(30, 64, 175)', // blue-800 (matches badge text color)
|
||||
main: 'rgb(59, 130, 246)', // blue-500 (matches enhanced API page)
|
||||
light: 'rgb(147, 197, 253)', // blue-300 (matches dark mode badge text)
|
||||
},
|
||||
POST: {
|
||||
main: 'rgb(34, 197, 94)', // green-500 (matches enhanced API page)
|
||||
light: 'rgb(134, 239, 172)', // green-300 (matches dark mode badge text)
|
||||
},
|
||||
DELETE: {
|
||||
main: 'rgb(153, 27, 27)', // red-800 (matches badge text color)
|
||||
main: 'rgb(239, 68, 68)', // red-500 (matches enhanced API page)
|
||||
light: 'rgb(252, 165, 165)', // red-300 (matches dark mode badge text)
|
||||
},
|
||||
PATCH: {
|
||||
main: 'rgb(154, 52, 18)', // orange-800 (matches badge text color)
|
||||
light: 'rgb(253, 186, 116)', // orange-300 (matches dark mode badge text)
|
||||
main: 'rgb(234, 179, 8)', // yellow-500 (matches enhanced API page)
|
||||
light: 'rgb(253, 224, 71)', // yellow-300 (matches dark mode badge text)
|
||||
},
|
||||
PUT: {
|
||||
main: 'rgb(154, 52, 18)', // orange-800 (same as PATCH)
|
||||
light: 'rgb(253, 186, 116)', // orange-300 (same as PATCH)
|
||||
main: 'rgb(249, 115, 22)', // orange-500 (matches enhanced API page)
|
||||
light: 'rgb(253, 186, 116)', // orange-300 (matches dark mode badge text)
|
||||
},
|
||||
} as const;
|
||||
|
||||
@ -118,31 +118,33 @@ function useAccordionState(key: string, defaultValue: boolean) {
|
||||
return [isOpen, setIsOpen] as const;
|
||||
}
|
||||
|
||||
// HTTP Method Badge Component
|
||||
// HTTP Method Badge Component - matches enhanced-api-page.tsx styling
|
||||
function HttpMethodBadge({ method }: { method: 'GET' | 'POST' | 'PATCH' | 'DELETE' | 'PUT' }) {
|
||||
const getBadgeStyles = (method: string) => {
|
||||
switch (method) {
|
||||
case 'GET': {
|
||||
return 'bg-green-100 text-green-800 border-green-200 dark:bg-green-900/30 dark:text-green-300 dark:border-green-700';
|
||||
return 'from-blue-500 to-blue-600 text-white shadow-blue-500/25';
|
||||
}
|
||||
case 'POST': {
|
||||
return 'bg-blue-100 text-blue-800 border-blue-200 dark:bg-blue-900/30 dark:text-blue-300 dark:border-blue-700';
|
||||
return 'from-green-500 to-green-600 text-white shadow-green-500/25';
|
||||
}
|
||||
case 'PATCH':
|
||||
case 'PUT': {
|
||||
return 'bg-orange-100 text-orange-800 border-orange-200 dark:bg-orange-900/30 dark:text-orange-300 dark:border-orange-700';
|
||||
return 'from-orange-500 to-orange-600 text-white shadow-orange-500/25';
|
||||
}
|
||||
case 'PATCH': {
|
||||
return 'from-yellow-500 to-yellow-600 text-white shadow-yellow-500/25';
|
||||
}
|
||||
case 'DELETE': {
|
||||
return 'bg-red-100 text-red-800 border-red-200 dark:bg-red-900/30 dark:text-red-300 dark:border-red-700';
|
||||
return 'from-red-500 to-red-600 text-white shadow-red-500/25';
|
||||
}
|
||||
default: {
|
||||
return 'bg-gray-100 text-gray-800 border-gray-200 dark:bg-gray-900/30 dark:text-gray-300 dark:border-gray-700';
|
||||
return 'from-gray-500 to-gray-600 text-white shadow-gray-500/25';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<span className={`inline-flex items-center justify-center px-1 py-0.5 rounded text-[10px] font-medium border ${getBadgeStyles(method)} leading-none w-10 flex-shrink-0`}>
|
||||
<span className={`inline-flex items-center justify-center px-1.5 py-0.5 rounded bg-gradient-to-r ${getBadgeStyles(method)} font-mono font-bold text-[9px] tracking-wide leading-none w-10 flex-shrink-0`}>
|
||||
{method}
|
||||
</span>
|
||||
);
|
||||
@ -904,7 +906,7 @@ export function ApiSidebarContent({ pages = [] }: { pages?: PageData[] }) {
|
||||
{/* eslint-disable-next-line @typescript-eslint/no-unnecessary-condition */}
|
||||
{!isMainSidebarCollapsed ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="inline-flex items-center px-1 py-0.5 rounded text-xs font-medium border bg-purple-100 text-purple-800 border-purple-200 dark:bg-purple-900/30 dark:text-purple-300 dark:border-purple-700 leading-none">
|
||||
<span className="inline-flex items-center justify-center px-1.5 py-0.5 rounded bg-gradient-to-r from-purple-500 to-purple-600 text-white shadow-purple-500/25 font-mono font-bold text-[9px] tracking-wide leading-none w-10 flex-shrink-0">
|
||||
EVENT
|
||||
</span>
|
||||
<span>{title}</span>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user