@atproto/oauth-provider 0.2.17 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- package/CHANGELOG.md +22 -0
- package/dist/client/client-manager.d.ts.map +1 -1
- package/dist/client/client-manager.js +6 -8
- package/dist/client/client-manager.js.map +1 -1
- package/dist/device/device-data.d.ts +7 -8
- package/dist/device/device-data.d.ts.map +1 -1
- package/dist/device/device-data.js +3 -2
- package/dist/device/device-data.js.map +1 -1
- package/dist/device/device-manager.d.ts +104 -19
- package/dist/device/device-manager.d.ts.map +1 -1
- package/dist/device/device-manager.js +43 -30
- package/dist/device/device-manager.js.map +1 -1
- package/dist/lib/http/accept.d.ts +2 -1
- package/dist/lib/http/accept.d.ts.map +1 -1
- package/dist/lib/http/accept.js.map +1 -1
- package/dist/lib/http/method.d.ts +1 -1
- package/dist/lib/http/method.d.ts.map +1 -1
- package/dist/lib/http/request.d.ts +10 -1
- package/dist/lib/http/request.d.ts.map +1 -1
- package/dist/lib/http/request.js +38 -0
- package/dist/lib/http/request.js.map +1 -1
- package/dist/lib/http/response.d.ts +3 -2
- package/dist/lib/http/response.d.ts.map +1 -1
- package/dist/lib/http/response.js.map +1 -1
- package/dist/lib/http/route.d.ts +2 -1
- package/dist/lib/http/route.d.ts.map +1 -1
- package/dist/lib/http/route.js.map +1 -1
- package/dist/lib/http/router.d.ts +2 -1
- package/dist/lib/http/router.d.ts.map +1 -1
- package/dist/lib/http/router.js.map +1 -1
- package/dist/lib/http/stream.d.ts +1 -1
- package/dist/lib/http/stream.d.ts.map +1 -1
- package/dist/lib/http/types.d.ts +0 -1
- package/dist/lib/http/types.d.ts.map +1 -1
- package/dist/lib/util/function.d.ts +8 -0
- package/dist/lib/util/function.d.ts.map +1 -1
- package/dist/lib/util/function.js +1 -1
- package/dist/lib/util/function.js.map +1 -1
- package/dist/lib/util/time.d.ts +7 -1
- package/dist/lib/util/time.d.ts.map +1 -1
- package/dist/lib/util/time.js +23 -12
- package/dist/lib/util/time.js.map +1 -1
- package/dist/oauth-hooks.d.ts +56 -4
- package/dist/oauth-hooks.d.ts.map +1 -1
- package/dist/oauth-hooks.js +8 -0
- package/dist/oauth-hooks.js.map +1 -1
- package/dist/oauth-provider.d.ts +10 -7
- package/dist/oauth-provider.d.ts.map +1 -1
- package/dist/oauth-provider.js +24 -23
- package/dist/oauth-provider.js.map +1 -1
- package/dist/output/output-manager.d.ts +1 -1
- package/dist/output/output-manager.d.ts.map +1 -1
- package/dist/output/send-authorize-redirect.d.ts +1 -1
- package/dist/output/send-authorize-redirect.d.ts.map +1 -1
- package/dist/output/send-web-page.d.ts +1 -1
- package/dist/output/send-web-page.d.ts.map +1 -1
- package/dist/request/request-manager.d.ts +2 -1
- package/dist/request/request-manager.d.ts.map +1 -1
- package/dist/request/request-manager.js +9 -1
- package/dist/request/request-manager.js.map +1 -1
- package/dist/token/token-manager.d.ts +3 -2
- package/dist/token/token-manager.d.ts.map +1 -1
- package/dist/token/token-manager.js +39 -24
- package/dist/token/token-manager.js.map +1 -1
- package/package.json +2 -4
- package/src/client/client-manager.ts +9 -11
- package/src/device/device-data.ts +3 -2
- package/src/device/device-manager.ts +93 -75
- package/src/lib/http/accept.ts +2 -6
- package/src/lib/http/method.ts +1 -1
- package/src/lib/http/request.ts +64 -1
- package/src/lib/http/response.ts +3 -2
- package/src/lib/http/route.ts +2 -1
- package/src/lib/http/router.ts +2 -1
- package/src/lib/http/stream.ts +1 -1
- package/src/lib/http/types.ts +0 -1
- package/src/lib/util/function.ts +19 -2
- package/src/lib/util/time.ts +35 -18
- package/src/oauth-hooks.ts +73 -13
- package/src/oauth-provider.ts +69 -28
- package/src/output/output-manager.ts +1 -1
- package/src/output/send-authorize-redirect.ts +1 -1
- package/src/output/send-web-page.ts +1 -1
- package/src/request/request-manager.ts +13 -2
- package/src/token/token-manager.ts +52 -23
- package/tsconfig.backend.tsbuildinfo +1 -1
- package/dist/device/device-details.d.ts +0 -16
- package/dist/device/device-details.d.ts.map +0 -1
- package/dist/device/device-details.js +0 -34
- package/dist/device/device-details.js.map +0 -1
- package/src/device/device-details.ts +0 -42
package/src/oauth-hooks.ts
CHANGED
@@ -11,21 +11,29 @@ import { ClientId } from './client/client-id.js'
|
|
11
11
|
import { ClientInfo } from './client/client-info.js'
|
12
12
|
import { Client } from './client/client.js'
|
13
13
|
import { InvalidAuthorizationDetailsError } from './errors/invalid-authorization-details-error.js'
|
14
|
+
import { RequestMetadata } from './lib/http/request.js'
|
14
15
|
import { Awaitable } from './lib/util/type.js'
|
16
|
+
import { AccessDeniedError, OAuthError } from './oauth-errors.js'
|
17
|
+
import { DeviceId } from './oauth-store.js'
|
15
18
|
|
16
19
|
// Make sure all types needed to implement the OAuthHooks are exported
|
17
|
-
export
|
18
|
-
|
20
|
+
export {
|
21
|
+
AccessDeniedError,
|
22
|
+
type Account,
|
23
|
+
type Awaitable,
|
19
24
|
Client,
|
20
|
-
ClientAuth,
|
21
|
-
ClientId,
|
22
|
-
ClientInfo,
|
25
|
+
type ClientAuth,
|
26
|
+
type ClientId,
|
27
|
+
type ClientInfo,
|
28
|
+
type DeviceId,
|
23
29
|
InvalidAuthorizationDetailsError,
|
24
|
-
Jwks,
|
25
|
-
OAuthAuthorizationDetails,
|
26
|
-
OAuthAuthorizationRequestParameters,
|
27
|
-
OAuthClientMetadata,
|
28
|
-
|
30
|
+
type Jwks,
|
31
|
+
type OAuthAuthorizationDetails,
|
32
|
+
type OAuthAuthorizationRequestParameters,
|
33
|
+
type OAuthClientMetadata,
|
34
|
+
OAuthError,
|
35
|
+
type OAuthTokenResponse,
|
36
|
+
type RequestMetadata,
|
29
37
|
}
|
30
38
|
|
31
39
|
export type OAuthHooks = {
|
@@ -36,10 +44,10 @@ export type OAuthHooks = {
|
|
36
44
|
* @throws {InvalidClientMetadataError} if the metadata is invalid
|
37
45
|
* @see {@link InvalidClientMetadataError}
|
38
46
|
*/
|
39
|
-
|
47
|
+
getClientInfo?: (
|
40
48
|
clientId: ClientId,
|
41
49
|
data: { metadata: OAuthClientMetadata; jwks?: Jwks },
|
42
|
-
) => Awaitable<
|
50
|
+
) => Awaitable<undefined | Partial<ClientInfo>>
|
43
51
|
|
44
52
|
/**
|
45
53
|
* Allows enriching the authorization details with additional information
|
@@ -47,9 +55,61 @@ export type OAuthHooks = {
|
|
47
55
|
*
|
48
56
|
* @see {@link https://datatracker.ietf.org/doc/html/rfc9396 | RFC 9396}
|
49
57
|
*/
|
50
|
-
|
58
|
+
getAuthorizationDetails?: (data: {
|
51
59
|
client: Client
|
60
|
+
clientAuth: ClientAuth
|
61
|
+
clientMetadata: RequestMetadata
|
52
62
|
parameters: OAuthAuthorizationRequestParameters
|
53
63
|
account: Account
|
54
64
|
}) => Awaitable<undefined | OAuthAuthorizationDetails>
|
65
|
+
|
66
|
+
/**
|
67
|
+
* This hook is called when a client is authorized.
|
68
|
+
*
|
69
|
+
* @throws {AccessDeniedError} to deny the authorization request and redirect
|
70
|
+
* the user to the client with an OAuth error (other errors will result in an
|
71
|
+
* internal server error being displayed to the user)
|
72
|
+
*
|
73
|
+
* @note We use `deviceMetadata` instead of `clientMetadata` to make it clear
|
74
|
+
* that this metadata is from the user device, which might be different from
|
75
|
+
* the client metadata (because the OAuth client could live in a backend).
|
76
|
+
*/
|
77
|
+
onAuthorized?: (data: {
|
78
|
+
client: Client
|
79
|
+
account: Account
|
80
|
+
parameters: OAuthAuthorizationRequestParameters
|
81
|
+
deviceId: DeviceId
|
82
|
+
deviceMetadata: RequestMetadata
|
83
|
+
}) => Awaitable<void>
|
84
|
+
|
85
|
+
/**
|
86
|
+
* This hook is called when an authorized client exchanges an authorization
|
87
|
+
* code for an access token.
|
88
|
+
*
|
89
|
+
* @throws {OAuthError} to cancel the token creation and revoke the session
|
90
|
+
*/
|
91
|
+
onTokenCreated?: (data: {
|
92
|
+
client: Client
|
93
|
+
clientAuth: ClientAuth
|
94
|
+
clientMetadata: RequestMetadata
|
95
|
+
account: Account
|
96
|
+
parameters: OAuthAuthorizationRequestParameters
|
97
|
+
/** null when "password grant" used (in which case {@link onAuthorized} won't have been called) */
|
98
|
+
deviceId: null | DeviceId
|
99
|
+
}) => Awaitable<void>
|
100
|
+
|
101
|
+
/**
|
102
|
+
* This hook is called when an authorized client refreshes an access token.
|
103
|
+
*
|
104
|
+
* @throws {OAuthError} to cancel the token refresh and revoke the session
|
105
|
+
*/
|
106
|
+
onTokenRefreshed?: (data: {
|
107
|
+
client: Client
|
108
|
+
clientAuth: ClientAuth
|
109
|
+
clientMetadata: RequestMetadata
|
110
|
+
account: Account
|
111
|
+
parameters: OAuthAuthorizationRequestParameters
|
112
|
+
/** null when "password grant" used (in which case {@link onAuthorized} won't have been called) */
|
113
|
+
deviceId: null | DeviceId
|
114
|
+
}) => Awaitable<void>
|
55
115
|
}
|
package/src/oauth-provider.ts
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from 'node:http'
|
1
2
|
import { mediaType } from '@hapi/accept'
|
2
3
|
import createHttpError from 'http-errors'
|
3
4
|
import type { Redis, RedisOptions } from 'ioredis'
|
@@ -54,7 +55,7 @@ import { ClientStore, ifClientStore } from './client/client-store.js'
|
|
54
55
|
import { Client } from './client/client.js'
|
55
56
|
import { AUTHENTICATION_MAX_AGE, TOKEN_MAX_AGE } from './constants.js'
|
56
57
|
import { DeviceId } from './device/device-id.js'
|
57
|
-
import { DeviceManager } from './device/device-manager.js'
|
58
|
+
import { DeviceManager, DeviceManagerOptions } from './device/device-manager.js'
|
58
59
|
import { DeviceStore, asDeviceStore } from './device/device-store.js'
|
59
60
|
import { AccessDeniedError } from './errors/access-denied-error.js'
|
60
61
|
import { AccountSelectionRequiredError } from './errors/account-selection-required-error.js'
|
@@ -69,10 +70,8 @@ import { UnauthorizedClientError } from './errors/unauthorized-client-error.js'
|
|
69
70
|
import { WWWAuthenticateError } from './errors/www-authenticate-error.js'
|
70
71
|
import {
|
71
72
|
Handler,
|
72
|
-
IncomingMessage,
|
73
73
|
Middleware,
|
74
74
|
Router,
|
75
|
-
ServerResponse,
|
76
75
|
combineMiddlewares,
|
77
76
|
parseHttpRequest,
|
78
77
|
setupCsrfToken,
|
@@ -85,6 +84,7 @@ import {
|
|
85
84
|
validateSameOrigin,
|
86
85
|
writeJson,
|
87
86
|
} from './lib/http/index.js'
|
87
|
+
import { RequestMetadata } from './lib/http/request.js'
|
88
88
|
import { dateToEpoch, dateToRelativeSeconds } from './lib/util/date.js'
|
89
89
|
import { Override } from './lib/util/type.js'
|
90
90
|
import { CustomMetadata, buildMetadata } from './metadata/build-metadata.js'
|
@@ -134,7 +134,7 @@ export {
|
|
134
134
|
export type RouterOptions<
|
135
135
|
Req extends IncomingMessage = IncomingMessage,
|
136
136
|
Res extends ServerResponse = ServerResponse,
|
137
|
-
> = {
|
137
|
+
> = DeviceManagerOptions & {
|
138
138
|
onError?: (req: Req, res: Res, err: unknown, message: string) => void
|
139
139
|
}
|
140
140
|
|
@@ -529,9 +529,10 @@ export class OAuthProvider extends OAuthVerifier {
|
|
529
529
|
* @see {@link https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-11#section-4.1.1}
|
530
530
|
*/
|
531
531
|
protected async authorize(
|
532
|
-
|
533
|
-
credentials: OAuthClientCredentialsNone,
|
532
|
+
clientCredentials: OAuthClientCredentialsNone,
|
534
533
|
query: OAuthAuthorizationRequestQuery,
|
534
|
+
deviceId: DeviceId,
|
535
|
+
deviceMetadata: RequestMetadata,
|
535
536
|
): Promise<AuthorizationResultRedirect | AuthorizationResultAuthorize> {
|
536
537
|
const { issuer } = this
|
537
538
|
|
@@ -546,7 +547,7 @@ export class OAuthProvider extends OAuthVerifier {
|
|
546
547
|
: null
|
547
548
|
|
548
549
|
const client = await this.clientManager
|
549
|
-
.getClient(
|
550
|
+
.getClient(clientCredentials.client_id)
|
550
551
|
.catch(accessDeniedCatcher)
|
551
552
|
|
552
553
|
const { clientAuth, parameters, uri } =
|
@@ -580,10 +581,11 @@ export class OAuthProvider extends OAuthVerifier {
|
|
580
581
|
}
|
581
582
|
|
582
583
|
const code = await this.requestManager.setAuthorized(
|
583
|
-
client,
|
584
584
|
uri,
|
585
|
-
|
585
|
+
client,
|
586
586
|
ssoSession.account,
|
587
|
+
deviceId,
|
588
|
+
deviceMetadata,
|
587
589
|
)
|
588
590
|
|
589
591
|
return { issuer, client, parameters, redirect: { code } }
|
@@ -596,10 +598,11 @@ export class OAuthProvider extends OAuthVerifier {
|
|
596
598
|
const ssoSession = ssoSessions[0]!
|
597
599
|
if (!ssoSession.loginRequired && !ssoSession.consentRequired) {
|
598
600
|
const code = await this.requestManager.setAuthorized(
|
599
|
-
client,
|
600
601
|
uri,
|
601
|
-
|
602
|
+
client,
|
602
603
|
ssoSession.account,
|
604
|
+
deviceId,
|
605
|
+
deviceMetadata,
|
603
606
|
)
|
604
607
|
|
605
608
|
return { issuer, client, parameters, redirect: { code } }
|
@@ -720,10 +723,11 @@ export class OAuthProvider extends OAuthVerifier {
|
|
720
723
|
}
|
721
724
|
|
722
725
|
protected async acceptRequest(
|
723
|
-
deviceId: DeviceId,
|
724
726
|
uri: RequestUri,
|
725
727
|
clientId: ClientId,
|
726
728
|
sub: string,
|
729
|
+
deviceId: DeviceId,
|
730
|
+
deviceMetadata: RequestMetadata,
|
727
731
|
): Promise<AuthorizationResultRedirect> {
|
728
732
|
const { issuer } = this
|
729
733
|
const client = await this.clientManager.getClient(clientId)
|
@@ -746,10 +750,11 @@ export class OAuthProvider extends OAuthVerifier {
|
|
746
750
|
}
|
747
751
|
|
748
752
|
const code = await this.requestManager.setAuthorized(
|
749
|
-
client,
|
750
753
|
uri,
|
751
|
-
|
754
|
+
client,
|
752
755
|
account,
|
756
|
+
deviceId,
|
757
|
+
deviceMetadata,
|
753
758
|
)
|
754
759
|
|
755
760
|
await this.accountManager.addAuthorizedClient(
|
@@ -791,11 +796,13 @@ export class OAuthProvider extends OAuthVerifier {
|
|
791
796
|
}
|
792
797
|
|
793
798
|
protected async token(
|
794
|
-
|
799
|
+
clientCredentials: OAuthClientCredentials,
|
800
|
+
clientMetadata: RequestMetadata,
|
795
801
|
request: OAuthTokenRequest,
|
796
802
|
dpopJkt: null | string,
|
797
803
|
): Promise<OAuthTokenResponse> {
|
798
|
-
const [client, clientAuth] =
|
804
|
+
const [client, clientAuth] =
|
805
|
+
await this.authenticateClient(clientCredentials)
|
799
806
|
|
800
807
|
if (!this.metadata.grant_types_supported?.includes(request.grant_type)) {
|
801
808
|
throw new InvalidGrantError(
|
@@ -810,11 +817,23 @@ export class OAuthProvider extends OAuthVerifier {
|
|
810
817
|
}
|
811
818
|
|
812
819
|
if (request.grant_type === 'authorization_code') {
|
813
|
-
return this.codeGrant(
|
820
|
+
return this.codeGrant(
|
821
|
+
client,
|
822
|
+
clientAuth,
|
823
|
+
clientMetadata,
|
824
|
+
request,
|
825
|
+
dpopJkt,
|
826
|
+
)
|
814
827
|
}
|
815
828
|
|
816
829
|
if (request.grant_type === 'refresh_token') {
|
817
|
-
return this.refreshTokenGrant(
|
830
|
+
return this.refreshTokenGrant(
|
831
|
+
client,
|
832
|
+
clientAuth,
|
833
|
+
clientMetadata,
|
834
|
+
request,
|
835
|
+
dpopJkt,
|
836
|
+
)
|
818
837
|
}
|
819
838
|
|
820
839
|
throw new InvalidGrantError(
|
@@ -825,6 +844,7 @@ export class OAuthProvider extends OAuthVerifier {
|
|
825
844
|
protected async codeGrant(
|
826
845
|
client: Client,
|
827
846
|
clientAuth: ClientAuth,
|
847
|
+
clientMetadata: RequestMetadata,
|
828
848
|
input: OAuthAuthorizationCodeGrantTokenRequest,
|
829
849
|
dpopJkt: null | string,
|
830
850
|
): Promise<OAuthTokenResponse> {
|
@@ -868,6 +888,7 @@ export class OAuthProvider extends OAuthVerifier {
|
|
868
888
|
return await this.tokenManager.create(
|
869
889
|
client,
|
870
890
|
clientAuth,
|
891
|
+
clientMetadata,
|
871
892
|
account,
|
872
893
|
{ id: deviceId, info },
|
873
894
|
parameters,
|
@@ -890,10 +911,17 @@ export class OAuthProvider extends OAuthVerifier {
|
|
890
911
|
async refreshTokenGrant(
|
891
912
|
client: Client,
|
892
913
|
clientAuth: ClientAuth,
|
914
|
+
clientMetadata: RequestMetadata,
|
893
915
|
input: OAuthRefreshTokenGrantTokenRequest,
|
894
916
|
dpopJkt: null | string,
|
895
917
|
): Promise<OAuthTokenResponse> {
|
896
|
-
return this.tokenManager.refresh(
|
918
|
+
return this.tokenManager.refresh(
|
919
|
+
client,
|
920
|
+
clientAuth,
|
921
|
+
clientMetadata,
|
922
|
+
input,
|
923
|
+
dpopJkt,
|
924
|
+
)
|
897
925
|
}
|
898
926
|
|
899
927
|
/**
|
@@ -998,7 +1026,7 @@ export class OAuthProvider extends OAuthVerifier {
|
|
998
1026
|
Req extends IncomingMessage = IncomingMessage,
|
999
1027
|
Res extends ServerResponse = ServerResponse,
|
1000
1028
|
>(options?: RouterOptions<Req, Res>) {
|
1001
|
-
const deviceManager = new DeviceManager(this.deviceStore)
|
1029
|
+
const deviceManager = new DeviceManager(this.deviceStore, options)
|
1002
1030
|
const outputManager = new OutputManager(this.customization)
|
1003
1031
|
|
1004
1032
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
@@ -1224,7 +1252,9 @@ export class OAuthProvider extends OAuthVerifier {
|
|
1224
1252
|
jsonHandler(async function (req, _res) {
|
1225
1253
|
const payload = await parseHttpRequest(req, ['json', 'urlencoded'])
|
1226
1254
|
|
1227
|
-
const
|
1255
|
+
const clientMetadata = await deviceManager.getRequestMetadata(req)
|
1256
|
+
|
1257
|
+
const clientCredentials = await oauthClientCredentialsSchema
|
1228
1258
|
.parseAsync(payload, { path: ['body'] })
|
1229
1259
|
.catch(throwInvalidClient)
|
1230
1260
|
|
@@ -1238,7 +1268,12 @@ export class OAuthProvider extends OAuthVerifier {
|
|
1238
1268
|
this.url,
|
1239
1269
|
)
|
1240
1270
|
|
1241
|
-
return server.token(
|
1271
|
+
return server.token(
|
1272
|
+
clientCredentials,
|
1273
|
+
clientMetadata,
|
1274
|
+
tokenRequest,
|
1275
|
+
dpopJkt,
|
1276
|
+
)
|
1242
1277
|
}),
|
1243
1278
|
)
|
1244
1279
|
|
@@ -1313,11 +1348,11 @@ export class OAuthProvider extends OAuthVerifier {
|
|
1313
1348
|
|
1314
1349
|
const query = Object.fromEntries(this.url.searchParams)
|
1315
1350
|
|
1316
|
-
const
|
1351
|
+
const clientCredentials = await oauthClientCredentialsSchema
|
1317
1352
|
.parseAsync(query, { path: ['body'] })
|
1318
1353
|
.catch(throwInvalidRequest)
|
1319
1354
|
|
1320
|
-
if ('client_secret' in
|
1355
|
+
if ('client_secret' in clientCredentials) {
|
1321
1356
|
throw new InvalidRequestError('Client secret must not be provided')
|
1322
1357
|
}
|
1323
1358
|
|
@@ -1325,10 +1360,15 @@ export class OAuthProvider extends OAuthVerifier {
|
|
1325
1360
|
.parseAsync(query, { path: ['query'] })
|
1326
1361
|
.catch(throwInvalidRequest)
|
1327
1362
|
|
1328
|
-
const { deviceId } = await deviceManager.load(req, res)
|
1363
|
+
const { deviceId, deviceMetadata } = await deviceManager.load(req, res)
|
1329
1364
|
|
1330
1365
|
const data = await server
|
1331
|
-
.authorize(
|
1366
|
+
.authorize(
|
1367
|
+
clientCredentials,
|
1368
|
+
authorizationRequest,
|
1369
|
+
deviceId,
|
1370
|
+
deviceMetadata,
|
1371
|
+
)
|
1332
1372
|
.catch((err) => accessDeniedToRedirectCatcher(req, res, err))
|
1333
1373
|
|
1334
1374
|
switch (true) {
|
@@ -1431,14 +1471,15 @@ export class OAuthProvider extends OAuthVerifier {
|
|
1431
1471
|
true,
|
1432
1472
|
)
|
1433
1473
|
|
1434
|
-
const { deviceId } = await deviceManager.load(req, res)
|
1474
|
+
const { deviceId, deviceMetadata } = await deviceManager.load(req, res)
|
1435
1475
|
|
1436
1476
|
const data = await server
|
1437
1477
|
.acceptRequest(
|
1438
|
-
deviceId,
|
1439
1478
|
input.request_uri,
|
1440
1479
|
input.client_id,
|
1441
1480
|
input.account_sub,
|
1481
|
+
deviceId,
|
1482
|
+
deviceMetadata,
|
1442
1483
|
)
|
1443
1484
|
.catch((err) => accessDeniedToRedirectCatcher(req, res, err))
|
1444
1485
|
|
@@ -20,6 +20,8 @@ import { InvalidGrantError } from '../errors/invalid-grant-error.js'
|
|
20
20
|
import { InvalidParametersError } from '../errors/invalid-parameters-error.js'
|
21
21
|
import { InvalidRequestError } from '../errors/invalid-request-error.js'
|
22
22
|
import { InvalidScopeError } from '../errors/invalid-scope-error.js'
|
23
|
+
import { RequestMetadata } from '../lib/http/request.js'
|
24
|
+
import { callAsync } from '../lib/util/function.js'
|
23
25
|
import { OAuthHooks } from '../oauth-hooks.js'
|
24
26
|
import { Signer } from '../signer/signer.js'
|
25
27
|
import { Code, generateCode } from './code.js'
|
@@ -369,10 +371,11 @@ export class RequestManager {
|
|
369
371
|
}
|
370
372
|
|
371
373
|
async setAuthorized(
|
372
|
-
client: Client,
|
373
374
|
uri: RequestUri,
|
374
|
-
|
375
|
+
client: Client,
|
375
376
|
account: Account,
|
377
|
+
deviceId: DeviceId,
|
378
|
+
deviceMetadata: RequestMetadata,
|
376
379
|
): Promise<Code> {
|
377
380
|
const id = decodeRequestUri(uri)
|
378
381
|
|
@@ -413,6 +416,14 @@ export class RequestManager {
|
|
413
416
|
expiresAt: new Date(Date.now() + AUTHORIZATION_INACTIVITY_TIMEOUT),
|
414
417
|
})
|
415
418
|
|
419
|
+
await callAsync(this.hooks.onAuthorized, {
|
420
|
+
client,
|
421
|
+
account,
|
422
|
+
parameters: data.parameters,
|
423
|
+
deviceId,
|
424
|
+
deviceMetadata,
|
425
|
+
})
|
426
|
+
|
416
427
|
return code
|
417
428
|
} catch (err) {
|
418
429
|
await this.store.deleteRequest(id)
|
@@ -29,6 +29,7 @@ import { InvalidDpopProofError } from '../errors/invalid-dpop-proof-error.js'
|
|
29
29
|
import { InvalidGrantError } from '../errors/invalid-grant-error.js'
|
30
30
|
import { InvalidRequestError } from '../errors/invalid-request-error.js'
|
31
31
|
import { InvalidTokenError } from '../errors/invalid-token-error.js'
|
32
|
+
import { RequestMetadata } from '../lib/http/request.js'
|
32
33
|
import { dateToEpoch, dateToRelativeSeconds } from '../lib/util/date.js'
|
33
34
|
import { callAsync } from '../lib/util/function.js'
|
34
35
|
import { OAuthHooks } from '../oauth-hooks.js'
|
@@ -82,6 +83,7 @@ export class TokenManager {
|
|
82
83
|
async create(
|
83
84
|
client: Client,
|
84
85
|
clientAuth: ClientAuth,
|
86
|
+
clientMetadata: RequestMetadata,
|
85
87
|
account: Account,
|
86
88
|
device: null | { id: DeviceId; info: DeviceAccountInfo },
|
87
89
|
parameters: OAuthAuthorizationRequestParameters,
|
@@ -207,13 +209,16 @@ export class TokenManager {
|
|
207
209
|
const now = new Date()
|
208
210
|
const expiresAt = this.createTokenExpiry(now)
|
209
211
|
|
210
|
-
const authorizationDetails =
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
212
|
+
const authorizationDetails = await callAsync(
|
213
|
+
this.hooks.getAuthorizationDetails,
|
214
|
+
{
|
215
|
+
client,
|
216
|
+
clientAuth,
|
217
|
+
clientMetadata,
|
218
|
+
parameters,
|
219
|
+
account,
|
220
|
+
},
|
221
|
+
)
|
217
222
|
|
218
223
|
const tokenData: TokenData = {
|
219
224
|
createdAt: now,
|
@@ -246,7 +251,7 @@ export class TokenManager {
|
|
246
251
|
authorization_details: authorizationDetails,
|
247
252
|
})
|
248
253
|
|
249
|
-
|
254
|
+
const response = await this.buildTokenResponse(
|
250
255
|
client,
|
251
256
|
accessToken,
|
252
257
|
refreshToken,
|
@@ -255,6 +260,17 @@ export class TokenManager {
|
|
255
260
|
account,
|
256
261
|
authorizationDetails,
|
257
262
|
)
|
263
|
+
|
264
|
+
await callAsync(this.hooks.onTokenCreated, {
|
265
|
+
client,
|
266
|
+
clientAuth,
|
267
|
+
clientMetadata,
|
268
|
+
account,
|
269
|
+
parameters,
|
270
|
+
deviceId: device ? device.id : null,
|
271
|
+
})
|
272
|
+
|
273
|
+
return response
|
258
274
|
} catch (err) {
|
259
275
|
// Just in case the token could not be issued, we delete it from the store
|
260
276
|
await this.store.deleteToken(tokenId)
|
@@ -316,6 +332,7 @@ export class TokenManager {
|
|
316
332
|
async refresh(
|
317
333
|
client: Client,
|
318
334
|
clientAuth: ClientAuth,
|
335
|
+
clientMetadata: RequestMetadata,
|
319
336
|
input: OAuthRefreshTokenGrantTokenRequest,
|
320
337
|
dpopJkt: null | string,
|
321
338
|
): Promise<OAuthTokenResponse> {
|
@@ -377,13 +394,16 @@ export class TokenManager {
|
|
377
394
|
throw new InvalidGrantError(`Refresh token expired`)
|
378
395
|
}
|
379
396
|
|
380
|
-
const
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
397
|
+
const authorizationDetails = await callAsync(
|
398
|
+
this.hooks.getAuthorizationDetails,
|
399
|
+
{
|
400
|
+
client,
|
401
|
+
clientAuth,
|
402
|
+
clientMetadata,
|
403
|
+
parameters,
|
404
|
+
account,
|
405
|
+
},
|
406
|
+
)
|
387
407
|
|
388
408
|
const nextTokenId = await generateTokenId()
|
389
409
|
const nextRefreshToken = await generateRefreshToken()
|
@@ -426,24 +446,33 @@ export class TokenManager {
|
|
426
446
|
iat: now,
|
427
447
|
jti: nextTokenId,
|
428
448
|
cnf: parameters.dpop_jkt ? { jkt: parameters.dpop_jkt } : undefined,
|
429
|
-
authorization_details,
|
449
|
+
authorization_details: authorizationDetails,
|
430
450
|
})
|
431
451
|
|
432
|
-
|
452
|
+
const response = await this.buildTokenResponse(
|
433
453
|
client,
|
434
454
|
accessToken,
|
435
455
|
nextRefreshToken,
|
436
456
|
expiresAt,
|
437
457
|
parameters,
|
438
458
|
account,
|
439
|
-
|
459
|
+
authorizationDetails,
|
440
460
|
)
|
461
|
+
|
462
|
+
await callAsync(this.hooks.onTokenRefreshed, {
|
463
|
+
client,
|
464
|
+
clientAuth,
|
465
|
+
clientMetadata,
|
466
|
+
account,
|
467
|
+
parameters,
|
468
|
+
deviceId: tokenInfo.data.deviceId,
|
469
|
+
})
|
470
|
+
|
471
|
+
return response
|
441
472
|
} catch (err) {
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
await this.store.deleteToken(tokenInfo.id)
|
446
|
-
}
|
473
|
+
// Just in case the token could not be refreshed, we delete it from the store
|
474
|
+
await this.store.deleteToken(tokenInfo.id)
|
475
|
+
|
447
476
|
throw err
|
448
477
|
}
|
449
478
|
}
|
@@ -1 +1 @@
|
|
1
|
-
{"root":["./src/constants.ts","./src/index.ts","./src/oauth-client.ts","./src/oauth-dpop.ts","./src/oauth-errors.ts","./src/oauth-hooks.ts","./src/oauth-provider.ts","./src/oauth-store.ts","./src/oauth-verifier.ts","./src/access-token/access-token-type.ts","./src/account/account-manager.ts","./src/account/account-store.ts","./src/account/account.ts","./src/assets/asset.ts","./src/assets/assets-middleware.ts","./src/assets/index.ts","./src/client/client-auth.ts","./src/client/client-data.ts","./src/client/client-id.ts","./src/client/client-info.ts","./src/client/client-manager.ts","./src/client/client-store.ts","./src/client/client-utils.ts","./src/client/client.ts","./src/device/device-data.ts","./src/device/device-
|
1
|
+
{"root":["./src/constants.ts","./src/index.ts","./src/oauth-client.ts","./src/oauth-dpop.ts","./src/oauth-errors.ts","./src/oauth-hooks.ts","./src/oauth-provider.ts","./src/oauth-store.ts","./src/oauth-verifier.ts","./src/access-token/access-token-type.ts","./src/account/account-manager.ts","./src/account/account-store.ts","./src/account/account.ts","./src/assets/asset.ts","./src/assets/assets-middleware.ts","./src/assets/index.ts","./src/client/client-auth.ts","./src/client/client-data.ts","./src/client/client-id.ts","./src/client/client-info.ts","./src/client/client-manager.ts","./src/client/client-store.ts","./src/client/client-utils.ts","./src/client/client.ts","./src/device/device-data.ts","./src/device/device-id.ts","./src/device/device-manager.ts","./src/device/device-store.ts","./src/device/session-id.ts","./src/dpop/dpop-manager.ts","./src/dpop/dpop-nonce.ts","./src/errors/access-denied-error.ts","./src/errors/account-selection-required-error.ts","./src/errors/consent-required-error.ts","./src/errors/invalid-authorization-details-error.ts","./src/errors/invalid-client-error.ts","./src/errors/invalid-client-id-error.ts","./src/errors/invalid-client-metadata-error.ts","./src/errors/invalid-dpop-key-binding-error.ts","./src/errors/invalid-dpop-proof-error.ts","./src/errors/invalid-grant-error.ts","./src/errors/invalid-parameters-error.ts","./src/errors/invalid-redirect-uri-error.ts","./src/errors/invalid-request-error.ts","./src/errors/invalid-scope-error.ts","./src/errors/invalid-token-error.ts","./src/errors/login-required-error.ts","./src/errors/oauth-error.ts","./src/errors/second-authentication-factor-required-error.ts","./src/errors/unauthorized-client-error.ts","./src/errors/use-dpop-nonce-error.ts","./src/errors/www-authenticate-error.ts","./src/lib/redis.ts","./src/lib/html/build-document.ts","./src/lib/html/escapers.ts","./src/lib/html/html.ts","./src/lib/html/index.ts","./src/lib/html/tags.ts","./src/lib/html/util.ts","./src/lib/http/accept.ts","./src/lib/http/context.ts","./src/lib/http/index.ts","./src/lib/http/method.ts","./src/lib/http/middleware.ts","./src/lib/http/parser.ts","./src/lib/http/path.ts","./src/lib/http/request.ts","./src/lib/http/response.ts","./src/lib/http/route.ts","./src/lib/http/router.ts","./src/lib/http/stream.ts","./src/lib/http/types.ts","./src/lib/http/url.ts","./src/lib/util/authorization-header.ts","./src/lib/util/cast.ts","./src/lib/util/crypto.ts","./src/lib/util/date.ts","./src/lib/util/function.ts","./src/lib/util/hostname.ts","./src/lib/util/redirect-uri.ts","./src/lib/util/time.ts","./src/lib/util/type.ts","./src/lib/util/well-known.ts","./src/metadata/build-metadata.ts","./src/oidc/sub.ts","./src/output/build-authorize-data.ts","./src/output/build-error-payload.ts","./src/output/customization.ts","./src/output/output-manager.ts","./src/output/send-authorize-redirect.ts","./src/output/send-web-page.ts","./src/replay/replay-manager.ts","./src/replay/replay-store-memory.ts","./src/replay/replay-store-redis.ts","./src/replay/replay-store.ts","./src/request/code.ts","./src/request/request-data.ts","./src/request/request-id.ts","./src/request/request-info.ts","./src/request/request-manager.ts","./src/request/request-store-memory.ts","./src/request/request-store-redis.ts","./src/request/request-store.ts","./src/request/request-uri.ts","./src/signer/signed-token-payload.ts","./src/signer/signer.ts","./src/token/refresh-token.ts","./src/token/token-claims.ts","./src/token/token-data.ts","./src/token/token-id.ts","./src/token/token-manager.ts","./src/token/token-store.ts","./src/token/verify-token-claims.ts"],"version":"5.6.3"}
|
@@ -1,16 +0,0 @@
|
|
1
|
-
import { IncomingMessage } from 'node:http';
|
2
|
-
import { z } from 'zod';
|
3
|
-
export declare const deviceDetailsSchema: z.ZodObject<{
|
4
|
-
userAgent: z.ZodNullable<z.ZodString>;
|
5
|
-
ipAddress: z.ZodString;
|
6
|
-
}, "strip", z.ZodTypeAny, {
|
7
|
-
userAgent: string | null;
|
8
|
-
ipAddress: string;
|
9
|
-
}, {
|
10
|
-
userAgent: string | null;
|
11
|
-
ipAddress: string;
|
12
|
-
}>;
|
13
|
-
export type DeviceDetails = z.infer<typeof deviceDetailsSchema>;
|
14
|
-
export declare function extractDeviceDetails(req: IncomingMessage, trustProxy: boolean): DeviceDetails;
|
15
|
-
export declare function extractIpAddress(req: IncomingMessage, trustProxy: boolean): string | undefined;
|
16
|
-
//# sourceMappingURL=device-details.d.ts.map
|
@@ -1 +0,0 @@
|
|
1
|
-
{"version":3,"file":"device-details.d.ts","sourceRoot":"","sources":["../../src/device/device-details.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAC3C,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,eAAO,MAAM,mBAAmB;;;;;;;;;EAG9B,CAAA;AACF,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAA;AAE/D,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,eAAe,EACpB,UAAU,EAAE,OAAO,GAClB,aAAa,CASf;AAED,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,eAAe,EACpB,UAAU,EAAE,OAAO,GAClB,MAAM,GAAG,SAAS,CAepB"}
|
@@ -1,34 +0,0 @@
|
|
1
|
-
"use strict";
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
exports.deviceDetailsSchema = void 0;
|
4
|
-
exports.extractDeviceDetails = extractDeviceDetails;
|
5
|
-
exports.extractIpAddress = extractIpAddress;
|
6
|
-
const zod_1 = require("zod");
|
7
|
-
exports.deviceDetailsSchema = zod_1.z.object({
|
8
|
-
userAgent: zod_1.z.string().nullable(),
|
9
|
-
ipAddress: zod_1.z.string(),
|
10
|
-
});
|
11
|
-
function extractDeviceDetails(req, trustProxy) {
|
12
|
-
const userAgent = req.headers['user-agent'] || null;
|
13
|
-
const ipAddress = extractIpAddress(req, trustProxy) || null;
|
14
|
-
if (!ipAddress) {
|
15
|
-
throw new Error('Could not determine IP address');
|
16
|
-
}
|
17
|
-
return { userAgent, ipAddress };
|
18
|
-
}
|
19
|
-
function extractIpAddress(req, trustProxy) {
|
20
|
-
// Express app compatibility
|
21
|
-
if ('ip' in req && typeof req.ip === 'string') {
|
22
|
-
return req.ip;
|
23
|
-
}
|
24
|
-
if (trustProxy) {
|
25
|
-
const forwardedFor = req.headers['x-forwarded-for'];
|
26
|
-
if (typeof forwardedFor === 'string') {
|
27
|
-
const firstForward = forwardedFor.split(',')[0].trim();
|
28
|
-
if (firstForward)
|
29
|
-
return firstForward;
|
30
|
-
}
|
31
|
-
}
|
32
|
-
return req.socket.remoteAddress;
|
33
|
-
}
|
34
|
-
//# sourceMappingURL=device-details.js.map
|