@atproto/oauth-provider 0.19.6 → 0.19.8
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/CHANGELOG.md +33 -0
- package/dist/errors/error-parser.js +5 -5
- package/dist/errors/error-parser.js.map +1 -1
- package/package.json +22 -18
- package/src/access-token/access-token-mode.ts +0 -4
- package/src/account/account-manager.ts +0 -591
- package/src/account/account-store.ts +0 -305
- package/src/account/sign-in-data.ts +0 -15
- package/src/account/sign-up-input.ts +0 -20
- package/src/client/client-auth.ts +0 -64
- package/src/client/client-data.ts +0 -9
- package/src/client/client-id.ts +0 -4
- package/src/client/client-info.ts +0 -13
- package/src/client/client-manager.ts +0 -772
- package/src/client/client-store.ts +0 -35
- package/src/client/client-utils.ts +0 -37
- package/src/client/client.ts +0 -394
- package/src/constants.ts +0 -80
- package/src/customization/branding.ts +0 -12
- package/src/customization/build-customization-css.ts +0 -55
- package/src/customization/build-customization-data.ts +0 -24
- package/src/customization/colors.ts +0 -48
- package/src/customization/customization.ts +0 -29
- package/src/customization/links.ts +0 -10
- package/src/device/device-data.ts +0 -11
- package/src/device/device-id.ts +0 -27
- package/src/device/device-manager.ts +0 -292
- package/src/device/device-store.ts +0 -31
- package/src/device/session-id.ts +0 -21
- package/src/dpop/dpop-manager.ts +0 -214
- package/src/dpop/dpop-nonce.ts +0 -121
- package/src/dpop/dpop-proof.ts +0 -6
- package/src/errors/access-denied-error.ts +0 -12
- package/src/errors/account-selection-required-error.ts +0 -12
- package/src/errors/authorization-error.ts +0 -45
- package/src/errors/consent-required-error.ts +0 -12
- package/src/errors/error-parser.ts +0 -147
- package/src/errors/handle-unavailable-error.ts +0 -23
- package/src/errors/invalid-authorization-details-error.ts +0 -27
- package/src/errors/invalid-client-error.ts +0 -20
- package/src/errors/invalid-client-id-error.ts +0 -31
- package/src/errors/invalid-client-metadata-error.ts +0 -68
- package/src/errors/invalid-credentials-error.ts +0 -29
- package/src/errors/invalid-dpop-key-binding-error.ts +0 -21
- package/src/errors/invalid-dpop-proof-error.ts +0 -13
- package/src/errors/invalid-grant-error.ts +0 -21
- package/src/errors/invalid-invite-code-error.ts +0 -10
- package/src/errors/invalid-redirect-uri-error.ts +0 -17
- package/src/errors/invalid-request-error.ts +0 -32
- package/src/errors/invalid-scope-error.ts +0 -15
- package/src/errors/invalid-token-error.ts +0 -60
- package/src/errors/login-required-error.ts +0 -12
- package/src/errors/oauth-error.ts +0 -28
- package/src/errors/second-authentication-factor-required-error.ts +0 -25
- package/src/errors/unauthorized-client-error.ts +0 -20
- package/src/errors/use-dpop-nonce-error.ts +0 -32
- package/src/errors/www-authenticate-error.ts +0 -64
- package/src/index.ts +0 -16
- package/src/lexicon/lexicon-data.ts +0 -11
- package/src/lexicon/lexicon-getter.ts +0 -55
- package/src/lexicon/lexicon-manager.ts +0 -116
- package/src/lexicon/lexicon-store.ts +0 -35
- package/src/lib/csp/index.ts +0 -101
- package/src/lib/hcaptcha.ts +0 -228
- package/src/lib/html/README.md +0 -9
- package/src/lib/html/build-document.ts +0 -151
- package/src/lib/html/escapers.ts +0 -66
- package/src/lib/html/html.ts +0 -56
- package/src/lib/html/hydration-data.ts +0 -19
- package/src/lib/html/index.ts +0 -5
- package/src/lib/html/tags.ts +0 -67
- package/src/lib/html/util.ts +0 -17
- package/src/lib/http/README.md +0 -11
- package/src/lib/http/accept.ts +0 -90
- package/src/lib/http/context.ts +0 -42
- package/src/lib/http/headers.ts +0 -15
- package/src/lib/http/index.ts +0 -10
- package/src/lib/http/method.ts +0 -18
- package/src/lib/http/middleware.ts +0 -169
- package/src/lib/http/parser.ts +0 -101
- package/src/lib/http/path.ts +0 -82
- package/src/lib/http/request.ts +0 -256
- package/src/lib/http/response.ts +0 -122
- package/src/lib/http/route.ts +0 -60
- package/src/lib/http/router.ts +0 -117
- package/src/lib/http/security-headers.ts +0 -100
- package/src/lib/http/stream.ts +0 -57
- package/src/lib/http/types.ts +0 -16
- package/src/lib/http/url.ts +0 -23
- package/src/lib/nsid.ts +0 -10
- package/src/lib/redis.ts +0 -25
- package/src/lib/util/authorization-header.ts +0 -28
- package/src/lib/util/cast.ts +0 -18
- package/src/lib/util/color.ts +0 -168
- package/src/lib/util/crypto.ts +0 -32
- package/src/lib/util/date.ts +0 -7
- package/src/lib/util/error.ts +0 -7
- package/src/lib/util/function.ts +0 -39
- package/src/lib/util/locale.ts +0 -12
- package/src/lib/util/object.ts +0 -23
- package/src/lib/util/redirect-uri.ts +0 -46
- package/src/lib/util/time.ts +0 -49
- package/src/lib/util/type.ts +0 -182
- package/src/lib/util/ui8.ts +0 -14
- package/src/lib/util/well-known.ts +0 -8
- package/src/lib/util/zod-error.ts +0 -26
- package/src/lib/write-form-redirect.ts +0 -58
- package/src/lib/write-html.ts +0 -70
- package/src/metadata/build-metadata.ts +0 -141
- package/src/oauth-client.ts +0 -3
- package/src/oauth-dpop.ts +0 -2
- package/src/oauth-errors.ts +0 -26
- package/src/oauth-hooks.ts +0 -519
- package/src/oauth-middleware.ts +0 -53
- package/src/oauth-provider.ts +0 -1110
- package/src/oauth-store.ts +0 -12
- package/src/oauth-verifier.ts +0 -260
- package/src/replay/replay-manager.ts +0 -51
- package/src/replay/replay-store-memory.ts +0 -36
- package/src/replay/replay-store-redis.ts +0 -30
- package/src/replay/replay-store.ts +0 -44
- package/src/request/code.ts +0 -23
- package/src/request/request-data.ts +0 -36
- package/src/request/request-id.ts +0 -22
- package/src/request/request-manager.ts +0 -521
- package/src/request/request-store.ts +0 -60
- package/src/request/request-uri.ts +0 -42
- package/src/result/authorization-redirect-parameters.ts +0 -24
- package/src/result/authorization-result-authorize-page.ts +0 -16
- package/src/result/authorization-result-redirect.ts +0 -8
- package/src/router/assets/assets-manifest.ts +0 -117
- package/src/router/assets/assets.ts +0 -124
- package/src/router/assets/csrf.ts +0 -79
- package/src/router/assets/send-account-page.ts +0 -23
- package/src/router/assets/send-authorization-page.ts +0 -42
- package/src/router/assets/send-cookie-error-page.ts +0 -21
- package/src/router/assets/send-error-page.ts +0 -25
- package/src/router/assets/send-redirect.ts +0 -140
- package/src/router/create-account-page-middleware.ts +0 -88
- package/src/router/create-api-middleware.ts +0 -1051
- package/src/router/create-authorization-page-middleware.ts +0 -281
- package/src/router/create-oauth-middleware.ts +0 -257
- package/src/router/error-handler.ts +0 -6
- package/src/router/middleware-options.ts +0 -9
- package/src/signer/access-token-payload.ts +0 -25
- package/src/signer/api-token-payload.ts +0 -18
- package/src/signer/signer.ts +0 -121
- package/src/token/refresh-token.ts +0 -30
- package/src/token/token-claims.ts +0 -22
- package/src/token/token-data.ts +0 -40
- package/src/token/token-id.ts +0 -25
- package/src/token/token-manager.ts +0 -427
- package/src/token/token-store.ts +0 -88
- package/src/types/authorization-response-error.ts +0 -27
- package/src/types/color-hue.ts +0 -3
- package/src/types/email-otp.ts +0 -3
- package/src/types/email.ts +0 -26
- package/src/types/handle.ts +0 -23
- package/src/types/invite-code.ts +0 -4
- package/src/types/par-response-error.ts +0 -25
- package/src/types/password.ts +0 -4
- package/src/types/rgb-color.ts +0 -21
- package/tsconfig.build.json +0 -8
- package/tsconfig.build.tsbuildinfo +0 -1
- package/tsconfig.json +0 -4
package/src/lib/util/type.ts
DELETED
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
export type Simplify<T> = { [K in keyof T]: T[K] } & {}
|
|
2
|
-
export type Override<T, V> = Simplify<{
|
|
3
|
-
[K in keyof (V & T)]: K extends keyof V
|
|
4
|
-
? V[K]
|
|
5
|
-
: K extends keyof T
|
|
6
|
-
? T[K]
|
|
7
|
-
: never
|
|
8
|
-
}>
|
|
9
|
-
export type Awaitable<T> = T | Promise<T>
|
|
10
|
-
export type NonNullableKeys<T, K extends keyof T> = Simplify<
|
|
11
|
-
OmitKey<T, K> & {
|
|
12
|
-
[P in K]-?: NonNullable<T[P]>
|
|
13
|
-
}
|
|
14
|
-
>
|
|
15
|
-
/**
|
|
16
|
-
* When a type has an `[x: string]: unknown` index signature, in addition to
|
|
17
|
-
* some known properties, using {@link Omit} will result in a type that only has
|
|
18
|
-
* the index signature, and no known properties.
|
|
19
|
-
*
|
|
20
|
-
* ```ts
|
|
21
|
-
* Omit<{ a: 3; b: 4; [x: string]: unknown }, 'a'> // { [x: string]: unknown }
|
|
22
|
-
* ```
|
|
23
|
-
*
|
|
24
|
-
* In order to properly omit specific known properties from a type with an index
|
|
25
|
-
* signature, we need to use another utility type that will behave correctly.
|
|
26
|
-
*
|
|
27
|
-
* ```ts
|
|
28
|
-
* OmitKey<{ a: 3; b: 4; [x: string]: unknown }, 'a'> // { b: 4; [x: string]: unknown }
|
|
29
|
-
* ```
|
|
30
|
-
*/
|
|
31
|
-
export type OmitKey<T, K extends keyof T> = {
|
|
32
|
-
[K2 in keyof T as K2 extends K ? never : K2]: T[K2]
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export type RequiredKey<T, K extends keyof T = never> = Simplify<
|
|
36
|
-
T & {
|
|
37
|
-
[L in K]-?: unknown extends T[L]
|
|
38
|
-
? NonNullable<unknown> | null
|
|
39
|
-
: Exclude<T[L], undefined>
|
|
40
|
-
}
|
|
41
|
-
>
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Converts a tuple to the equivalent type of combining every item into a single
|
|
45
|
-
* one. If any of the item in the tuple is non nullish, the result will be non
|
|
46
|
-
* nullish.
|
|
47
|
-
*/
|
|
48
|
-
export type CombinedTuple<T extends readonly unknown[]> = T extends []
|
|
49
|
-
? undefined
|
|
50
|
-
: Exclude<
|
|
51
|
-
T[number],
|
|
52
|
-
// If any item in the tuple is never `null` (resp. `undefined`), exclude
|
|
53
|
-
// `null` (resp. `undefined`) from `T[number]`
|
|
54
|
-
{
|
|
55
|
-
[K in keyof T]-?:
|
|
56
|
-
| (null extends T[K] ? never : null)
|
|
57
|
-
| (undefined extends T[K] ? never : undefined)
|
|
58
|
-
}[keyof T]
|
|
59
|
-
>
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Similar to {@link Required} but also ensures that all values are defined.
|
|
63
|
-
*/
|
|
64
|
-
export type RequiredDefined<T> = { [K in keyof T]-?: Exclude<T[K], undefined> }
|
|
65
|
-
|
|
66
|
-
// <hardcore-mode> (don't touch this)
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* @example
|
|
70
|
-
* ```ts
|
|
71
|
-
* type F = UnionToFnUnion<'a' | 'b'> // (() => 'a') | (() => 'b')
|
|
72
|
-
* ```
|
|
73
|
-
*/
|
|
74
|
-
type UnionToFnUnion<T> = T extends any ? () => T : never
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* @example
|
|
78
|
-
* ```ts
|
|
79
|
-
* type A = UnionToIntersection<(() => 'a') | (() => 'b')> // (() => 'a') & (() => 'b')
|
|
80
|
-
*
|
|
81
|
-
* UnionToIntersection<{ foo: string | number } | { foo: number; bar: 4 }> // { foo: number; bar: 4 }
|
|
82
|
-
* ```
|
|
83
|
-
*/
|
|
84
|
-
type UnionToIntersection<T> = (T extends any ? (x: T) => void : never) extends (
|
|
85
|
-
x: infer U,
|
|
86
|
-
) => void
|
|
87
|
-
? U
|
|
88
|
-
: never
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* @example
|
|
92
|
-
* ```ts
|
|
93
|
-
* type B = ExtractUnionItem<'a' | 'b'> // 'b'
|
|
94
|
-
* ```
|
|
95
|
-
*/
|
|
96
|
-
type ExtractUnionItem<T> =
|
|
97
|
-
// There exists a quirk in the way TypeScript works when inferring return
|
|
98
|
-
// types of an (disjoined) intersection of functions:
|
|
99
|
-
//
|
|
100
|
-
// type AnB = (() => 'a') & (() => 'b')
|
|
101
|
-
// type B = AnB extends () => infer R ? R : never // 'b'
|
|
102
|
-
//
|
|
103
|
-
// By turning the input union T (e.g. 'a' | 'b') into a union of function
|
|
104
|
-
// (() => 'a') | (() => 'b') and then into an intersection of those functions
|
|
105
|
-
// (() => 'a') & (() => 'b'), we can exploit the special TypeScript behavior
|
|
106
|
-
// to infer only the last return type from the functions, which is effectively
|
|
107
|
-
// equal to the last item of the input union T.
|
|
108
|
-
UnionToIntersection<UnionToFnUnion<T>> extends () => infer R ? R : never
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Utility that turn a union of types (`'a' | 'b'`) into a tuple with matching
|
|
112
|
-
* types (`['a', 'b']`).
|
|
113
|
-
*
|
|
114
|
-
* @note this only work with unions of "const" types. Using this with globals
|
|
115
|
-
* types (`string`, etc.) will yield unexpected results.
|
|
116
|
-
*
|
|
117
|
-
* @example
|
|
118
|
-
* ```ts
|
|
119
|
-
* type T = UnionToTuple<'a' | 'b'> // ['a', 'b']
|
|
120
|
-
* type T = UnionToTuple<'a' | 'b' | 'c'> // ['a', 'b', 'c']
|
|
121
|
-
* ```
|
|
122
|
-
*/
|
|
123
|
-
type UnionToTuple<T> = UnionToTupleInternal<T>
|
|
124
|
-
|
|
125
|
-
type UnionToTupleInternal<
|
|
126
|
-
T,
|
|
127
|
-
// Accumulator for terminal recursivity (initialized to empty tuple)
|
|
128
|
-
Acc extends readonly any[] = [],
|
|
129
|
-
// Get the next item from the union (if any)
|
|
130
|
-
Next = ExtractUnionItem<T>,
|
|
131
|
-
> =
|
|
132
|
-
// If there were no more items to extract from the union T, then we are done
|
|
133
|
-
[Next] extends [never]
|
|
134
|
-
? // Return result of previous recursive calls
|
|
135
|
-
Acc
|
|
136
|
-
: // Recursively call UnionToTupleInternal by Exclude'ing the Next item from
|
|
137
|
-
// the union (T) and adding it to the accumulator.
|
|
138
|
-
UnionToTupleInternal<Exclude<T, Next>, readonly [Next, ...Acc]>
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* This utility allows to create an assertion function that checks if a
|
|
142
|
-
* particular interface is fully implemented by some value.
|
|
143
|
-
*
|
|
144
|
-
* The use of the (rather complex) {@link UnionToTuple} allows to ensure that,
|
|
145
|
-
* at runtime, all the required interface keys are indeed checked, and that
|
|
146
|
-
* future additions to the interface do not result in a false sense of type
|
|
147
|
-
* safety.
|
|
148
|
-
*
|
|
149
|
-
* @note This function should not be made public, as it relies on a quirk of
|
|
150
|
-
* TypeScript to work properly.
|
|
151
|
-
*
|
|
152
|
-
* @example Valid use
|
|
153
|
-
*
|
|
154
|
-
* ```ts
|
|
155
|
-
* const isFoo = buildInterfaceChecker<{ foo: string }>(['foo'])
|
|
156
|
-
* const isFooBar = buildInterfaceChecker<{ foo: string; bar: boolean }>([
|
|
157
|
-
* 'foo',
|
|
158
|
-
* 'bar',
|
|
159
|
-
* ])
|
|
160
|
-
*
|
|
161
|
-
* declare const val: { foo?: string }
|
|
162
|
-
*
|
|
163
|
-
* if (isFoo(val)) {
|
|
164
|
-
* val // { foo: string }
|
|
165
|
-
* }
|
|
166
|
-
* ```
|
|
167
|
-
*
|
|
168
|
-
* @example Use cases where the runtime keys do not match the interface keys
|
|
169
|
-
*
|
|
170
|
-
* ```ts
|
|
171
|
-
* buildInterfaceChecker<{ foo: string }>([])
|
|
172
|
-
* buildInterfaceChecker<{ foo: string }>(['fee'])
|
|
173
|
-
* buildInterfaceChecker<{ foo: string; bar: string }>(['foo'])
|
|
174
|
-
* buildInterfaceChecker<{ foo: string; bar: string }>(['foo', 'baz'])
|
|
175
|
-
* ```
|
|
176
|
-
*/
|
|
177
|
-
export const buildInterfaceChecker =
|
|
178
|
-
<I extends object>(keys: readonly string[] & UnionToTuple<keyof I>) =>
|
|
179
|
-
<V extends Partial<I>>(value: V): value is V & RequiredDefined<I> =>
|
|
180
|
-
keys.every((name) => value[name] !== undefined)
|
|
181
|
-
|
|
182
|
-
// </hardcore-mode>
|
package/src/lib/util/ui8.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
export function parseUi8Hex(v: string) {
|
|
2
|
-
return asUi8(parseInt(v, 16))
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
export function parseUi8Dec(v: string) {
|
|
6
|
-
return asUi8(parseInt(v, 10))
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function asUi8(v: number) {
|
|
10
|
-
if (v >= 0 && v <= 255 && Number.isInteger(v)) return v
|
|
11
|
-
throw new TypeError(
|
|
12
|
-
`Invalid value "${v}" (expected an integer between 0 and 255)`,
|
|
13
|
-
)
|
|
14
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { ZodError, ZodIssue, ZodIssueCode } from 'zod'
|
|
2
|
-
|
|
3
|
-
export function formatZodError(err: ZodError, prefix?: string): string {
|
|
4
|
-
const message = err.issues.length
|
|
5
|
-
? err.issues.map(formatZodIssue).join('; ')
|
|
6
|
-
: err.message // Should never happen (issues should never be empty)
|
|
7
|
-
return prefix ? `${prefix}: ${message}` : message
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function formatZodIssue(issue: ZodIssue): string {
|
|
11
|
-
if (issue.code === ZodIssueCode.invalid_union) {
|
|
12
|
-
return issue.unionErrors
|
|
13
|
-
.map((err) => err.issues.map(formatZodIssue).join('; '))
|
|
14
|
-
.join(', or ')
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
if (issue.path.length === 1 && typeof issue.path[0] === 'number') {
|
|
18
|
-
return `${issue.message} at index ${issue.path[0]}`
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
if (issue.path.length) {
|
|
22
|
-
return `${issue.message} at ${issue.path.join('.')}`
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
return issue.message
|
|
26
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import type { ServerResponse } from 'node:http'
|
|
2
|
-
import { html, js } from './html/index.js'
|
|
3
|
-
import { setCookie } from './http/request.js'
|
|
4
|
-
import { SecurityHeadersOptions } from './http/security-headers.js'
|
|
5
|
-
import { writeHtml } from './write-html.js'
|
|
6
|
-
|
|
7
|
-
export type WriteFormRedirectOptions = SecurityHeadersOptions
|
|
8
|
-
|
|
9
|
-
// We prevent the user from coming "back" to this page and resubmitting the form
|
|
10
|
-
// repeatedly by disabling the submit button after the first submission.
|
|
11
|
-
const SCRIPT = js`
|
|
12
|
-
const form = document.forms[0];
|
|
13
|
-
|
|
14
|
-
let canSubmit = true;
|
|
15
|
-
|
|
16
|
-
form.addEventListener('submit', (event) => {
|
|
17
|
-
if (!canSubmit) {
|
|
18
|
-
event.preventDefault();
|
|
19
|
-
} else {
|
|
20
|
-
canSubmit = false;
|
|
21
|
-
}
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
setTimeout(() => {
|
|
25
|
-
form.submit();
|
|
26
|
-
}, 1);
|
|
27
|
-
`
|
|
28
|
-
|
|
29
|
-
// @NOTE If translations and design are needed, consider replacing this with a
|
|
30
|
-
// web app page.
|
|
31
|
-
|
|
32
|
-
export function writeFormRedirect(
|
|
33
|
-
res: ServerResponse,
|
|
34
|
-
method: 'post' | 'get',
|
|
35
|
-
uri: string,
|
|
36
|
-
params: Iterable<[string, string]>,
|
|
37
|
-
options?: WriteFormRedirectOptions,
|
|
38
|
-
): void {
|
|
39
|
-
res.setHeader('Cache-Control', 'no-store')
|
|
40
|
-
|
|
41
|
-
// Prevent the Chrome from caching this page
|
|
42
|
-
// see: https://latesthackingnews.com/2023/12/12/google-updates-chrome-bfcache-for-faster-page-viewing/
|
|
43
|
-
setCookie(res, 'bfCacheBypass', 'foo', { maxAge: 1, sameSite: 'lax' })
|
|
44
|
-
|
|
45
|
-
return writeHtml(res, {
|
|
46
|
-
...options,
|
|
47
|
-
htmlAttrs: { lang: 'en' },
|
|
48
|
-
scripts: [SCRIPT],
|
|
49
|
-
body: html`
|
|
50
|
-
<form method="${method}" action="${uri}">
|
|
51
|
-
${Array.from(params, ([key, value]) => [
|
|
52
|
-
html`<input type="hidden" name="${key}" value="${value}" />`,
|
|
53
|
-
])}
|
|
54
|
-
<input type="submit" value="Continue" />
|
|
55
|
-
</form>
|
|
56
|
-
`,
|
|
57
|
-
})
|
|
58
|
-
}
|
package/src/lib/write-html.ts
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto'
|
|
2
|
-
import type { ServerResponse } from 'node:http'
|
|
3
|
-
import { CspValue, mergeCsp } from './csp/index.js'
|
|
4
|
-
import {
|
|
5
|
-
AssetRef,
|
|
6
|
-
BuildDocumentOptions,
|
|
7
|
-
Html,
|
|
8
|
-
buildDocument,
|
|
9
|
-
} from './html/index.js'
|
|
10
|
-
import { WriteResponseOptions, writeBuffer } from './http/response.js'
|
|
11
|
-
import {
|
|
12
|
-
SecurityHeadersOptions,
|
|
13
|
-
setSecurityHeaders,
|
|
14
|
-
} from './http/security-headers.js'
|
|
15
|
-
|
|
16
|
-
export type WriteHtmlOptions = BuildDocumentOptions &
|
|
17
|
-
WriteResponseOptions &
|
|
18
|
-
SecurityHeadersOptions
|
|
19
|
-
|
|
20
|
-
export function writeHtml(
|
|
21
|
-
res: ServerResponse,
|
|
22
|
-
options: WriteHtmlOptions,
|
|
23
|
-
): void {
|
|
24
|
-
// @NOTE the csp string might be quite long. In that case it might be tempting
|
|
25
|
-
// to set it through the http-equiv <meta> in the HTML. However, some
|
|
26
|
-
// directives cannot be enforced by browsers when set through the meta tag
|
|
27
|
-
// (e.g. 'frame-ancestors'). Therefore, it's better to set the CSP through the
|
|
28
|
-
// HTTP header.
|
|
29
|
-
const csp = mergeCsp(
|
|
30
|
-
{
|
|
31
|
-
// Keep "upgrade-insecure-requests" in sync with HSTS setting. HSTS is
|
|
32
|
-
// typically set to false for localhost endpoints. Chrome and FF will
|
|
33
|
-
// ignore "upgrade-insecure-requests" from localhost, but Safari will
|
|
34
|
-
// enforce it, requiring to be explicitly disable it for localhost.
|
|
35
|
-
'upgrade-insecure-requests': options.hsts !== false,
|
|
36
|
-
'default-src': ["'none'"],
|
|
37
|
-
'base-uri': options.base?.origin as undefined | `https://${string}`,
|
|
38
|
-
'script-src': options.scripts?.map(assetToCsp).filter((v) => v != null),
|
|
39
|
-
'style-src': options.styles?.map(assetToCsp).filter((v) => v != null),
|
|
40
|
-
},
|
|
41
|
-
options.csp,
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
const html = buildDocument(options).toString()
|
|
45
|
-
|
|
46
|
-
// HTML pages should always be served with safety protection headers
|
|
47
|
-
setSecurityHeaders(res, { ...options, csp })
|
|
48
|
-
writeBuffer(res, html, {
|
|
49
|
-
...options,
|
|
50
|
-
contentType: options?.contentType ?? 'text/html; charset=utf-8',
|
|
51
|
-
})
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function assetToCsp(asset?: Html | AssetRef): undefined | CspValue {
|
|
55
|
-
if (asset == null) return undefined
|
|
56
|
-
if (asset instanceof Html) {
|
|
57
|
-
// Inline assets are "allowed" by their hash
|
|
58
|
-
const hash = createHash('sha256')
|
|
59
|
-
for (const fragment of asset) hash.update(fragment)
|
|
60
|
-
return `'sha256-${hash.digest('base64')}'`
|
|
61
|
-
} else {
|
|
62
|
-
// External assets are referenced by their origin
|
|
63
|
-
if (asset.url.startsWith('https:') || asset.url.startsWith('http:')) {
|
|
64
|
-
return new URL(asset.url).origin as `https:${string}` | `http:${string}`
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Internal assets are served from the same origin
|
|
68
|
-
return `'self'`
|
|
69
|
-
}
|
|
70
|
-
}
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
import { Keyset } from '@atproto/jwk'
|
|
2
|
-
import {
|
|
3
|
-
OAuthAuthorizationServerMetadata,
|
|
4
|
-
OAuthIssuerIdentifier,
|
|
5
|
-
oauthAuthorizationServerMetadataValidator,
|
|
6
|
-
} from '@atproto/oauth-types'
|
|
7
|
-
import { Client } from '../client/client.js'
|
|
8
|
-
import { VERIFY_ALGOS } from '../lib/util/crypto.js'
|
|
9
|
-
|
|
10
|
-
export type CustomMetadata = {
|
|
11
|
-
authorization_details_types_supported?: string[]
|
|
12
|
-
protected_resources?: string[]
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* @see {@link https://datatracker.ietf.org/doc/html/rfc8414#section-2}
|
|
17
|
-
* @see {@link https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata}
|
|
18
|
-
* @see {@link https://openid.net/specs/openid-connect-prompt-create-1_0.html}
|
|
19
|
-
*/
|
|
20
|
-
export function buildMetadata(
|
|
21
|
-
issuer: OAuthIssuerIdentifier,
|
|
22
|
-
keyset: Keyset,
|
|
23
|
-
customMetadata?: CustomMetadata,
|
|
24
|
-
): OAuthAuthorizationServerMetadata {
|
|
25
|
-
return oauthAuthorizationServerMetadataValidator.parse({
|
|
26
|
-
issuer,
|
|
27
|
-
|
|
28
|
-
scopes_supported: [
|
|
29
|
-
'atproto',
|
|
30
|
-
|
|
31
|
-
// These serve as hint that this server supports the transitional scopes.
|
|
32
|
-
// This is not a specced behavior.
|
|
33
|
-
'transition:email',
|
|
34
|
-
'transition:generic',
|
|
35
|
-
'transition:chat.bsky',
|
|
36
|
-
|
|
37
|
-
// Other atproto scopes can't be enumerated as they are dynamic.
|
|
38
|
-
],
|
|
39
|
-
subject_types_supported: [
|
|
40
|
-
//
|
|
41
|
-
'public', // The same "sub" is returned for all clients
|
|
42
|
-
// 'pairwise', // A different "sub" is returned for each client
|
|
43
|
-
],
|
|
44
|
-
response_types_supported: [
|
|
45
|
-
// OAuth
|
|
46
|
-
'code',
|
|
47
|
-
// 'token',
|
|
48
|
-
|
|
49
|
-
// OpenID
|
|
50
|
-
// 'none',
|
|
51
|
-
// 'code id_token token',
|
|
52
|
-
// 'code id_token',
|
|
53
|
-
// 'code token',
|
|
54
|
-
// 'id_token token',
|
|
55
|
-
// 'id_token',
|
|
56
|
-
],
|
|
57
|
-
response_modes_supported: [
|
|
58
|
-
// https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#ResponseModes
|
|
59
|
-
'query',
|
|
60
|
-
'fragment',
|
|
61
|
-
// https://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html#FormPostResponseMode
|
|
62
|
-
'form_post',
|
|
63
|
-
],
|
|
64
|
-
grant_types_supported: [
|
|
65
|
-
//
|
|
66
|
-
'authorization_code',
|
|
67
|
-
'refresh_token',
|
|
68
|
-
],
|
|
69
|
-
code_challenge_methods_supported: [
|
|
70
|
-
// https://www.iana.org/assignments/oauth-parameters/oauth-parameters.xhtml#pkce-code-challenge-method
|
|
71
|
-
'S256',
|
|
72
|
-
|
|
73
|
-
// atproto does not allow "plain"
|
|
74
|
-
// 'plain',
|
|
75
|
-
],
|
|
76
|
-
ui_locales_supported: [
|
|
77
|
-
//
|
|
78
|
-
'en-US',
|
|
79
|
-
],
|
|
80
|
-
display_values_supported: [
|
|
81
|
-
//
|
|
82
|
-
'page',
|
|
83
|
-
'popup',
|
|
84
|
-
'touch',
|
|
85
|
-
// 'wap', LoL
|
|
86
|
-
],
|
|
87
|
-
|
|
88
|
-
// https://openid.net/specs/openid-connect-prompt-create-1_0.html
|
|
89
|
-
prompt_values_supported: [
|
|
90
|
-
'none',
|
|
91
|
-
'login',
|
|
92
|
-
'consent',
|
|
93
|
-
'select_account',
|
|
94
|
-
'create',
|
|
95
|
-
],
|
|
96
|
-
|
|
97
|
-
// https://datatracker.ietf.org/doc/html/rfc9207
|
|
98
|
-
authorization_response_iss_parameter_supported: true,
|
|
99
|
-
|
|
100
|
-
// https://datatracker.ietf.org/doc/html/rfc9101#section-4
|
|
101
|
-
request_object_signing_alg_values_supported: [...VERIFY_ALGOS, 'none'],
|
|
102
|
-
request_object_encryption_alg_values_supported: [], // None
|
|
103
|
-
request_object_encryption_enc_values_supported: [], // None
|
|
104
|
-
|
|
105
|
-
request_parameter_supported: true,
|
|
106
|
-
request_uri_parameter_supported: true,
|
|
107
|
-
require_request_uri_registration: true,
|
|
108
|
-
|
|
109
|
-
jwks_uri: new URL('/oauth/jwks', issuer).href,
|
|
110
|
-
|
|
111
|
-
authorization_endpoint: new URL('/oauth/authorize', issuer).href,
|
|
112
|
-
|
|
113
|
-
token_endpoint: new URL('/oauth/token', issuer).href,
|
|
114
|
-
token_endpoint_auth_methods_supported: [...Client.AUTH_METHODS_SUPPORTED],
|
|
115
|
-
token_endpoint_auth_signing_alg_values_supported: [...VERIFY_ALGOS],
|
|
116
|
-
|
|
117
|
-
revocation_endpoint: new URL('/oauth/revoke', issuer).href,
|
|
118
|
-
|
|
119
|
-
// @TODO Should we implement these endpoints?
|
|
120
|
-
// introspection_endpoint: new URL('/oauth/introspect', issuer).href,
|
|
121
|
-
// end_session_endpoint: new URL('/oauth/logout', issuer).href,
|
|
122
|
-
|
|
123
|
-
// https://datatracker.ietf.org/doc/html/rfc9126#section-5
|
|
124
|
-
pushed_authorization_request_endpoint: new URL('/oauth/par', issuer).href,
|
|
125
|
-
|
|
126
|
-
require_pushed_authorization_requests: true,
|
|
127
|
-
|
|
128
|
-
// https://datatracker.ietf.org/doc/html/rfc9449#section-5.1
|
|
129
|
-
dpop_signing_alg_values_supported: [...VERIFY_ALGOS],
|
|
130
|
-
|
|
131
|
-
// https://datatracker.ietf.org/doc/html/rfc9396#section-14.4
|
|
132
|
-
authorization_details_types_supported:
|
|
133
|
-
customMetadata?.authorization_details_types_supported,
|
|
134
|
-
|
|
135
|
-
// https://www.rfc-editor.org/rfc/rfc9728.html#section-4
|
|
136
|
-
protected_resources: customMetadata?.protected_resources,
|
|
137
|
-
|
|
138
|
-
// https://www.ietf.org/archive/id/draft-ietf-oauth-client-id-metadata-document-00.html
|
|
139
|
-
client_id_metadata_document_supported: true,
|
|
140
|
-
})
|
|
141
|
-
}
|
package/src/oauth-client.ts
DELETED
package/src/oauth-dpop.ts
DELETED
package/src/oauth-errors.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
// Root Error class
|
|
2
|
-
export { OAuthError } from './errors/oauth-error.js'
|
|
3
|
-
|
|
4
|
-
export * from './errors/access-denied-error.js'
|
|
5
|
-
export * from './errors/account-selection-required-error.js'
|
|
6
|
-
export * from './errors/authorization-error.js'
|
|
7
|
-
export * from './errors/consent-required-error.js'
|
|
8
|
-
export * from './errors/handle-unavailable-error.js'
|
|
9
|
-
export * from './errors/invalid-authorization-details-error.js'
|
|
10
|
-
export * from './errors/invalid-client-error.js'
|
|
11
|
-
export * from './errors/invalid-client-id-error.js'
|
|
12
|
-
export * from './errors/invalid-client-metadata-error.js'
|
|
13
|
-
export * from './errors/invalid-credentials-error.js'
|
|
14
|
-
export * from './errors/invalid-dpop-key-binding-error.js'
|
|
15
|
-
export * from './errors/invalid-dpop-proof-error.js'
|
|
16
|
-
export * from './errors/invalid-grant-error.js'
|
|
17
|
-
export * from './errors/invalid-invite-code-error.js'
|
|
18
|
-
export * from './errors/invalid-redirect-uri-error.js'
|
|
19
|
-
export * from './errors/invalid-request-error.js'
|
|
20
|
-
export * from './errors/invalid-scope-error.js'
|
|
21
|
-
export * from './errors/invalid-token-error.js'
|
|
22
|
-
export * from './errors/login-required-error.js'
|
|
23
|
-
export * from './errors/second-authentication-factor-required-error.js'
|
|
24
|
-
export * from './errors/unauthorized-client-error.js'
|
|
25
|
-
export * from './errors/use-dpop-nonce-error.js'
|
|
26
|
-
export * from './errors/www-authenticate-error.js'
|