-
Notifications
You must be signed in to change notification settings - Fork 1k
/
index.ts
175 lines (148 loc) · 4.84 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
export * from './parseJWT'
import type { APIGatewayProxyEvent, Context as LambdaContext } from 'aws-lambda'
import { parse as parseCookie } from 'cookie'
import { getEventHeader } from '../event'
import type { Decoded } from './parseJWT'
export type { Decoded }
// This is shared by `@redwoodjs/web` as well as used on auth middleware
export const AUTH_PROVIDER_HEADER = 'auth-provider'
export const getAuthProviderHeader = (
event: APIGatewayProxyEvent | Request,
) => {
const authProviderKey = Object.keys(event?.headers ?? {}).find(
(key) => key.toLowerCase() === AUTH_PROVIDER_HEADER,
)
if (authProviderKey) {
return getEventHeader(event, authProviderKey)
}
return undefined
}
export interface AuthorizationHeader {
schema: 'Bearer' | 'Basic' | string
token: string
}
export type AuthorizationCookies = {
parsedCookie: Record<string, string>
rawCookie: string
type: string
} | null
export const parseAuthorizationCookie = (
event: APIGatewayProxyEvent | Request,
): AuthorizationCookies => {
const cookie = getEventHeader(event, 'Cookie')
// Unauthenticated request
if (!cookie) {
return null
}
const parsedCookie = parseCookie(cookie)
return {
parsedCookie,
rawCookie: cookie,
// When not unauthenticated, this will be null/undefined
// Remember that the cookie header could contain other (unrelated) values!
type: parsedCookie[AUTH_PROVIDER_HEADER],
}
}
/**
* Split the `Authorization` header into a schema and token part.
*/
export const parseAuthorizationHeader = (
event: APIGatewayProxyEvent | Request,
): AuthorizationHeader => {
const parts = getEventHeader(event, 'Authorization')?.split(' ')
if (parts?.length !== 2) {
throw new Error('The `Authorization` header is not valid.')
}
const [schema, token] = parts
if (!schema.length || !token.length) {
throw new Error('The `Authorization` header is not valid.')
}
return { schema, token }
}
/** @MARK Note that we do not send LambdaContext when making fetch requests
*
* This part is incomplete, as we need to decide how we will make the breaking change to
* 1. getCurrentUser
* 2. authDecoders
*/
export type AuthContextPayload = [
Decoded,
{ type: string } & AuthorizationHeader,
// @MARK: Context is not passed when using middleware auth
{
event: APIGatewayProxyEvent | Request
context?: LambdaContext
},
]
export type Decoder = (
token: string,
type: string,
req: {
event: APIGatewayProxyEvent | Request
context?: LambdaContext
},
) => Promise<Decoded>
/**
* Get the authorization information from the request headers and request context.
* @returns [decoded, { type, schema, token }, { event, context }]
**/
export const getAuthenticationContext = async ({
authDecoder,
event,
context,
}: {
authDecoder?: Decoder | Decoder[]
event: APIGatewayProxyEvent | Request
context: LambdaContext
}): Promise<undefined | AuthContextPayload> => {
const cookieHeader = parseAuthorizationCookie(event)
const typeFromHeader = getAuthProviderHeader(event)
// Short-circuit - if no auth-provider or cookie header, its
// an unauthenticated request
if (!typeFromHeader && !cookieHeader) {
return undefined
}
// The actual session parsing is done by the auth decoder
let token: string | undefined
let type: string | undefined
let schema: string | undefined
// If there is a cookie header and the auth type is set in the cookie, use that
// There can be cases, such as with Supabase where its auth client sets the cookie and Bearer token
// but the project is not using cookie auth with an auth-provider cookie set
// So, cookie/ssr auth needs both the token and the auth-provider in cookies
if (cookieHeader?.type) {
token = cookieHeader.rawCookie
type = cookieHeader.type
schema = 'cookie'
// If type is set in the header, use Bearer token auth (priority 2)
} else if (typeFromHeader) {
const parsedAuthHeader = parseAuthorizationHeader(event as any)
token = parsedAuthHeader.token
type = typeFromHeader
schema = parsedAuthHeader.schema
}
// Unauthenticated request
if (!token || !type || !schema) {
return undefined
}
// Run through decoders until one returns a decoded payload
let authDecoders: Decoder[] = []
if (Array.isArray(authDecoder)) {
authDecoders = authDecoder
} else if (authDecoder) {
authDecoders = [authDecoder]
}
let decoded = null
let i = 0
while (!decoded && i < authDecoders.length) {
decoded = await authDecoders[i](token, type, {
// @MARK: When called from middleware, the decoder will pass Request, not Lambda event
event,
context,
})
i++
}
// @TODO should we rename token? It's not actually the token - its the cookie header -because
// some auth providers will have a cookie where we don't know the key
return [decoded, { type, schema, token }, { event, context }]
}