@adonis-agora/authkit-client 0.18.8 → 0.19.0

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.
@@ -1,42 +1,47 @@
1
1
  {{{
2
2
  exports({ to: app.makePath('app/controllers/oidc_session_controller.ts') })
3
3
  }}}
4
+ import { inject } from '@adonisjs/core'
4
5
  import type { HttpContext } from '@adonisjs/core/http'
5
- import { buildAuthorizeUrl, exchangeCode, generatePkce } from '@adonis-agora/authkit-client'
6
- import { randomUUID } from 'node:crypto'
6
+ import { AuthkitClientManager, OidcCallbackError } from '@adonis-agora/authkit-client'
7
7
 
8
+ /**
9
+ * Login, callback and logout written out by hand. If you do not need to customise
10
+ * them, `registerOidcClient(router)` registers the same three routes for you.
11
+ */
12
+ @inject()
8
13
  export default class OidcSessionController {
9
- async login(ctx: HttpContext) {
10
- const manager = await ctx.containerResolver.make('authkit.client')
11
- const cfg = manager.clientConfig
12
- const { verifier, challenge } = await generatePkce()
13
- const state = randomUUID()
14
- ctx.session.put('authkit_pkce', { verifier, state })
15
- const url = buildAuthorizeUrl({
16
- issuer: cfg.issuer, clientId: cfg.clientId, redirectUri: cfg.redirectUri,
17
- scopes: cfg.scopes, state, codeChallenge: challenge,
18
- })
19
- return ctx.response.redirect(url)
14
+ constructor(protected authkit: AuthkitClientManager) {}
15
+
16
+ async login({ session, response }: HttpContext) {
17
+ // Fresh state + nonce + PKCE, against the endpoints discovered from the issuer.
18
+ const { url, pending } = await this.authkit.createAuthorizationRequest()
19
+ session.put('authkit_pkce', pending)
20
+ return response.redirect(url)
20
21
  }
21
22
 
22
23
  async callback(ctx: HttpContext) {
23
- const manager = await ctx.containerResolver.make('authkit.client')
24
- const cfg = manager.clientConfig
25
- const { code, state } = ctx.request.qs()
26
- const pkce = ctx.session.get('authkit_pkce')
27
- if (!pkce || pkce.state !== state) return ctx.response.badRequest({ error: 'state inválido' })
28
- const tokenSet = await exchangeCode({
29
- issuer: cfg.issuer, clientId: cfg.clientId, clientSecret: cfg.clientSecret,
30
- redirectUri: cfg.redirectUri, code, codeVerifier: pkce.verifier,
31
- })
32
- manager.startSession(ctx, tokenSet)
33
- ctx.session.forget('authkit_pkce')
24
+ const pending = ctx.session.pull('authkit_pkce')
25
+ try {
26
+ // Checks state and iss, exchanges the code and validates the id_token (nonce included).
27
+ const { tokens: tokenSet } = await this.authkit.completeAuthorization({
28
+ pending,
29
+ callbackParams: ctx.request.qs(),
30
+ })
31
+ // startSession (not session.put) drops any impersonation parked by a previous session.
32
+ this.authkit.startSession(ctx, tokenSet)
33
+ } catch (error) {
34
+ // Stale callback, or the IdP sent the user back with an error (access_denied…).
35
+ if (error instanceof OidcCallbackError && error.restartable) {
36
+ return ctx.response.redirect('/auth/login')
37
+ }
38
+ throw error
39
+ }
34
40
  return ctx.response.redirect('/')
35
41
  }
36
42
 
37
43
  async logout(ctx: HttpContext) {
38
- const manager = await ctx.containerResolver.make('authkit.client')
39
- manager.endSession(ctx)
44
+ this.authkit.endSession(ctx)
40
45
  return ctx.response.redirect('/')
41
46
  }
42
47
  }