publishing: unfenced vault accessor for the amplify path
createPublishingCredentialAccessor is execution-fenced (Temporal contexts only) — the API-process amplify route threw EXECUTION_CREDENTIAL_FENCE. Use the established PgConnectionStore+vault accessor instead (same trust level as the archive amplify route's bot-token path).
This commit is contained in:
parent
6145281b3f
commit
96e5a63dd8
11 changed files with 1501 additions and 8 deletions
72
apps/api/src/oidc/oidc.controller.ts
Normal file
72
apps/api/src/oidc/oidc.controller.ts
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
import { Body, Controller, Get, Header, Headers, HttpCode, Inject, Post, Query, Redirect, Req, UnauthorizedException, UseGuards } from "@nestjs/common";
|
||||
import type { Request } from "express";
|
||||
import { RbacGuard, type AuthenticatedRequest } from "../rbac/rbac.guard.js";
|
||||
import { requireSession } from "../http/validation.js";
|
||||
import { OidcService, type TokenResponse, type UserInfo } from "./oidc.service.js";
|
||||
import type { PublicJwk } from "./oidc.keys.js";
|
||||
|
||||
/**
|
||||
* The Hub's OpenID Connect provider (issuer https://api.occupymedia.cc/oidc).
|
||||
*
|
||||
* GET /oidc/.well-known/openid-configuration discovery
|
||||
* GET /oidc/jwks public ES256 keys
|
||||
* GET /oidc/authorize validate, then 302 → Hub SPA consent page
|
||||
* POST /oidc/authorize/consent (Bearer session) issue the single-use code
|
||||
* POST /oidc/token code + PKCE verifier → tokens
|
||||
* GET /oidc/userinfo Bearer access_token → claims
|
||||
*
|
||||
* Only /authorize/consent is session-guarded: every active member may sign in
|
||||
* to files, so it needs no permission. See OidcService for the cookie-less
|
||||
* authorize flow (a deliberate deviation from the design doc).
|
||||
*/
|
||||
@Controller("oidc")
|
||||
export class OidcController {
|
||||
constructor(@Inject(OidcService) private readonly oidc: OidcService) {}
|
||||
|
||||
@Get(".well-known/openid-configuration")
|
||||
@Header("Cache-Control", "public, max-age=300")
|
||||
discovery(): Record<string, unknown> {
|
||||
return this.oidc.discovery();
|
||||
}
|
||||
|
||||
@Get("jwks")
|
||||
@Header("Cache-Control", "public, max-age=300")
|
||||
jwks(): Promise<{ keys: PublicJwk[] }> {
|
||||
return this.oidc.jwks();
|
||||
}
|
||||
|
||||
@Get("authorize")
|
||||
@Redirect()
|
||||
@Header("Cache-Control", "no-store")
|
||||
authorize(@Query() query: unknown): { url: string; statusCode: number } {
|
||||
return { url: this.oidc.authorizeRedirect(query), statusCode: 302 };
|
||||
}
|
||||
|
||||
@Post("authorize/consent")
|
||||
@HttpCode(200)
|
||||
@UseGuards(RbacGuard)
|
||||
@Header("Cache-Control", "no-store")
|
||||
consent(@Req() request: Request & AuthenticatedRequest, @Body() body: unknown): Promise<{ redirectTo: string }> {
|
||||
const session = requireSession(request);
|
||||
return this.oidc.consent(session.workspaceId, session.userId, body);
|
||||
}
|
||||
|
||||
@Post("token")
|
||||
@HttpCode(200)
|
||||
@Header("Cache-Control", "no-store")
|
||||
@Header("Pragma", "no-cache")
|
||||
token(@Body() body: unknown): Promise<TokenResponse> {
|
||||
return this.oidc.token(body);
|
||||
}
|
||||
|
||||
@Get("userinfo")
|
||||
@Header("Cache-Control", "no-store")
|
||||
async userinfo(@Req() request: Request, @Headers("authorization") authorization: string | undefined): Promise<UserInfo> {
|
||||
try {
|
||||
return await this.oidc.userinfo(authorization);
|
||||
} catch (err) {
|
||||
if (err instanceof UnauthorizedException) request.res?.setHeader("WWW-Authenticate", 'Bearer error="invalid_token"');
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue