@atproto/oauth-provider 0.2.17 → 0.3.1
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 +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
|