• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

supabase / storage / 25739744753

12 May 2026 02:05PM UTC coverage: 39.493% (-34.9%) from 74.366%
25739744753

Pull #1102

github

web-flow
Merge 8db6e774f into defbbb616
Pull Request #1102: fix: add more acceptance test coverage

2113 of 5922 branches covered (35.68%)

Branch coverage included in aggregate %.

4230 of 10139 relevant lines covered (41.72%)

35.36 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

5.71
/src/http/plugins/jwt.ts
1
import { verifyJWT, verifyJWTWithCache } from '@internal/auth'
2
import { getJwtSecret } from '@internal/database'
3
import { ERRORS } from '@internal/errors'
4
import { FastifyInstance } from 'fastify'
5
import fastifyPlugin from 'fastify-plugin'
6
import { JWTPayload } from 'jose'
7
import { getConfig } from '../../config'
8

9
declare module 'fastify' {
10
  interface FastifyRequest {
11
    isAuthenticated: boolean
12
    jwt: string
13
    jwtPayload?: JWTPayload & { role?: string }
14
    owner?: string
15
  }
16

17
  interface FastifyContextConfig {
18
    allowInvalidJwt?: boolean
19
  }
20
}
21

22
interface JWTPluginOptions {
23
  enforceJwtRoles?: string[]
24
  skipIfAlreadyAuthenticated?: boolean
25
}
26

27
const { jwtCachingEnabled } = getConfig()
3✔
28

29
const BEARER = /^Bearer\s+/i
3✔
30

31
const jwtPlugin = fastifyPlugin<JWTPluginOptions>(
3✔
32
  async (fastify, opts) => {
33
    fastify.decorateRequest('jwt', '')
×
34
    fastify.decorateRequest('jwtPayload', undefined)
×
35

36
    fastify.addHook('preHandler', async (request) => {
×
37
      if (opts.skipIfAlreadyAuthenticated && request.isAuthenticated && request.jwtPayload) {
×
38
        return
×
39
      }
40

41
      request.jwt = (request.headers.authorization || '').replace(BEARER, '')
×
42

43
      if (!request.jwt && request.routeOptions.config.allowInvalidJwt) {
×
44
        request.jwtPayload = { role: 'anon' }
×
45
        request.isAuthenticated = false
×
46
        return
×
47
      }
48

49
      const { secret, jwks } = await getJwtSecret(request.tenantId)
×
50

51
      try {
×
52
        const payload = await (jwtCachingEnabled
×
53
          ? verifyJWTWithCache(request.jwt, secret, jwks || null)
×
54
          : verifyJWT(request.jwt, secret, jwks || null))
×
55

56
        request.jwtPayload = payload
×
57
        request.owner = payload.sub
×
58
        request.isAuthenticated = true
×
59
      } catch (e) {
60
        request.jwtPayload = { role: 'anon' }
×
61
        request.isAuthenticated = false
×
62

63
        if (request.routeOptions.config.allowInvalidJwt) {
×
64
          return
×
65
        }
66
        const err = e as Error
×
67
        throw ERRORS.AccessDenied(err.message, err)
×
68
      }
69
    })
70

71
    if (opts.enforceJwtRoles && opts.enforceJwtRoles.length > 0) {
×
72
      fastify.register(enforceJwtRole, {
×
73
        roles: opts.enforceJwtRoles,
74
      })
75
    }
76
  },
77
  { name: 'auth-jwt' }
78
)
79

80
interface EnforceJWTRoleOptions {
81
  roles: string[]
82
}
83

84
export const enforceJwtRole = fastifyPlugin<EnforceJWTRoleOptions>(
3✔
85
  async (fastify, opts) => {
86
    fastify.addHook('preHandler', async (request) => {
×
87
      if (!request.isAuthenticated) {
×
88
        throw ERRORS.AccessDenied('Access denied: JWT is not authenticated').withStatusCode(403)
×
89
      }
90

91
      const hasRoles = request.jwtPayload?.role && opts.roles.includes(request.jwtPayload.role)
×
92

93
      if (!hasRoles) {
×
94
        throw ERRORS.AccessDenied(`Access denied: Invalid role`).withStatusCode(403)
×
95
      }
96
    })
97
  },
98
  { name: 'allow-invalid-jwt' }
99
)
100

101
export function registerJwtAuth(fastify: FastifyInstance, opts: JWTPluginOptions = {}) {
×
102
  fastify.addHook('onRoute', (routeOptions) => {
×
103
    routeOptions.schema = routeOptions.schema || {}
×
104
    routeOptions.schema.security = [{ bearerAuth: [] }]
×
105
  })
106
  fastify.register(jwtPlugin, opts)
×
107
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc