@reflagged/shell 1.7.0 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reflagged/shell",
3
- "version": "1.7.0",
3
+ "version": "1.7.1",
4
4
  "description": "Shared app shell for Reflagged services — OIDC auth, SSO, app switcher",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -55,13 +55,26 @@ export async function GET(req: Request): Promise<NextResponse> {
55
55
 
56
56
  const params = oauth.validateAuthResponse(as, client, url, pkce.state)
57
57
 
58
- const tokenResponse = await oauth.authorizationCodeGrantRequest(
59
- as, client, clientAuth, params, env.redirectUri, pkce.codeVerifier,
60
- )
61
- const result = await oauth.processAuthorizationCodeResponse(as, client, tokenResponse, {
62
- expectedNonce: pkce.nonce,
63
- requireIdToken: true,
64
- })
58
+ // The provider issued a code and may still refuse to exchange it. Seen live:
59
+ // after an "Einloggen als" session ended, the browser's provider session
60
+ // still belonged to the impersonated account, every code was minted for it,
61
+ // and every exchange came back invalid_grant — an unhandled throw, so a bare
62
+ // 500 on each reload.
63
+ let result: Awaited<ReturnType<typeof oauth.processAuthorizationCodeResponse>>
64
+ try {
65
+ const tokenResponse = await oauth.authorizationCodeGrantRequest(
66
+ as, client, clientAuth, params, env.redirectUri, pkce.codeVerifier,
67
+ )
68
+ result = await oauth.processAuthorizationCodeResponse(as, client, tokenResponse, {
69
+ expectedNonce: pkce.nonce,
70
+ requireIdToken: true,
71
+ })
72
+ } catch (error) {
73
+ if (!(error instanceof oauth.ResponseBodyError)) throw error
74
+ const res = deniedResponse(error.error, error.error_description ?? null)
75
+ res.cookies.delete(OIDC_STATE_COOKIE)
76
+ return res
77
+ }
65
78
 
66
79
  const claims = oauth.getValidatedIdTokenClaims(result)
67
80
  if (!claims) return NextResponse.json({ error: 'no-id-token' }, { status: 400 })
@@ -123,13 +136,25 @@ const escapeHtml = (v: string): string =>
123
136
  */
124
137
  export function deniedResponse(error: string, description: string | null): NextResponse {
125
138
  const status = error === 'access_denied' ? 403 : 400
139
+ // invalid_grant: the code belongs to a provider session that is no longer
140
+ // valid. Trying again lands in that same session, so the way out is to end
141
+ // it — signing out at the platform — not to ask an administrator.
142
+ const expired = error === 'invalid_grant'
126
143
  const headline =
127
- error === 'access_denied' ? 'Kein Zugriff auf diese Anwendung' : 'Anmeldung fehlgeschlagen'
128
- const detail =
129
- description?.trim() ||
130
- (error === 'access_denied'
131
- ? 'Ihr Account ist dieser Anwendung nicht zugeordnet.'
132
- : `Der Anmeldedienst hat die Anmeldung abgelehnt (${error}).`)
144
+ error === 'access_denied'
145
+ ? 'Kein Zugriff auf diese Anwendung'
146
+ : expired
147
+ ? 'Anmeldung abgelaufen'
148
+ : 'Anmeldung fehlgeschlagen'
149
+ const detail = expired
150
+ ? 'Die Anmeldung gehört zu einer Sitzung, die nicht mehr gilt — etwa nach dem Ende von „Einloggen als“. Melden Sie sich bei der Plattform ab und danach neu an.'
151
+ : description?.trim() ||
152
+ (error === 'access_denied'
153
+ ? 'Ihr Account ist dieser Anwendung nicht zugeordnet.'
154
+ : `Der Anmeldedienst hat die Anmeldung abgelehnt (${error}).`)
155
+ const next = expired
156
+ ? '<p><a href="/api/oidc/signout">Bei der Plattform abmelden</a></p>'
157
+ : '<p>Bitten Sie eine Administratorin oder einen Administrator Ihrer Organisation, Ihren Account für diese Anwendung freizuschalten.</p>\n <p><a href="/">Zur Startseite</a></p>'
133
158
  const html = `<!doctype html>
134
159
  <html lang="de">
135
160
  <head>
@@ -150,8 +175,7 @@ export function deniedResponse(error: string, description: string | null): NextR
150
175
  <main>
151
176
  <h1>${escapeHtml(headline)}</h1>
152
177
  <p>${escapeHtml(detail)}</p>
153
- <p>Bitten Sie eine Administratorin oder einen Administrator Ihrer Organisation, Ihren Account für diese Anwendung freizuschalten.</p>
154
- <p><a href="/">Zur Startseite</a></p>
178
+ ${next}
155
179
  <p><small>Fehlercode: ${escapeHtml(error)}</small></p>
156
180
  </main>
157
181
  </body>