1- import {
2- BadRequestHttpError ,
3- ForbiddenHttpError ,
4- IndexedStorage ,
5- JwkGenerator ,
6- matchesAuthorizationScheme ,
7- TypeObject ,
8- UnauthorizedHttpError
9- } from '@solid/community-server' ;
1+ import { BadRequestHttpError , } from '@solid/community-server' ;
102import { getLoggerFor } from 'global-logger-factory' ;
11- import { importJWK , SignJWT } from 'jose' ;
12- import ms , { StringValue } from 'ms' ;
13- import { randomUUID } from 'node:crypto' ;
143import { DialogInput } from '../dialog/Input' ;
154import { Negotiator } from '../dialog/Negotiator' ;
165import { NeedInfoError } from '../errors/NeedInfoError' ;
176import { HttpHandler , HttpHandlerContext , HttpHandlerResponse } from '../util/http/models/HttpHandler' ;
187import { reType } from '../util/ReType' ;
19- import { CLIENT_REGISTRATION_STORAGE_DESCRIPTION , CLIENT_REGISTRATION_STORAGE_TYPE } from './ClientRegistration ' ;
8+ import { UMA_PROTECTION_SCOPE } from './token/UmaProtection ' ;
209
21- export const GRANT_TYPE_CLIENT_CREDENTIALS = 'client_credentials' ;
22- export const GRANT_TYPE_REFRESH_TOKEN = 'refresh_token' ;
2310export const GRANT_TYPE_UMA_TICKET = 'urn:ietf:params:oauth:grant-type:uma-ticket' ;
2411
25- export const PAT_STORAGE_TYPE = 'pat' ;
26- export const PAT_STORAGE_DESCRIPTION = {
27- pat : 'string' ,
28- expiration : 'number' ,
29- refreshToken : 'string' ,
30- registration : `id:${ CLIENT_REGISTRATION_STORAGE_TYPE } ` ,
31- } as const ;
32-
3312/**
3413 * The TokenRequestHandler implements the interface of the UMA Token Endpoint.
3514 */
3615export class TokenRequestHandler extends HttpHandler {
3716 protected readonly logger = getLoggerFor ( this ) ;
38- protected readonly tokenExpiration : number ;
39- private readonly storage : IndexedStorage < {
40- [ CLIENT_REGISTRATION_STORAGE_TYPE ] : typeof CLIENT_REGISTRATION_STORAGE_DESCRIPTION ,
41- [ PAT_STORAGE_TYPE ] : typeof PAT_STORAGE_DESCRIPTION ,
42- } > ;
4317
4418 constructor (
4519 protected negotiator : Negotiator ,
46- storage : IndexedStorage < Record < string , never > > ,
47- protected readonly keyGen : JwkGenerator ,
48- protected readonly baseUrl : string ,
49- tokenExpiration : string = '30m' ,
20+ protected readonly umaProtection : HttpHandler ,
5021 ) {
5122 super ( ) ;
52- this . tokenExpiration = Math . floor ( ms ( tokenExpiration as StringValue ) / 1000 ) ;
53- this . storage = storage ;
54- this . initializeStorage ( ) ;
55- }
56-
57- protected async initializeStorage ( ) : Promise < void > {
58- await this . storage . defineType ( PAT_STORAGE_TYPE , PAT_STORAGE_DESCRIPTION ) ;
59- await this . storage . createIndex ( PAT_STORAGE_TYPE , 'refreshToken' ) ;
60- await this . storage . createIndex ( PAT_STORAGE_TYPE , 'pat' ) ;
61- await this . storage . createIndex ( PAT_STORAGE_TYPE , 'registration' ) ;
6223 }
6324
6425 public async handle ( input : HttpHandlerContext ) : Promise < HttpHandlerResponse < any > > {
@@ -71,9 +32,11 @@ export class TokenRequestHandler extends HttpHandler {
7132 throw new BadRequestHttpError ( `Invalid token request body: ${ e instanceof Error ? e . message : '' } ` ) ;
7233 }
7334
35+ if ( params . scope === UMA_PROTECTION_SCOPE ) {
36+ return this . umaProtection . handleSafe ( input ) ;
37+ }
38+
7439 switch ( params . grant_type ) {
75- case GRANT_TYPE_CLIENT_CREDENTIALS : return this . handlePatRequest ( params , input . request . headers . authorization ) ;
76- case GRANT_TYPE_REFRESH_TOKEN : return this . handleRefreshRequest ( params , input . request . headers . authorization ) ;
7740 case GRANT_TYPE_UMA_TICKET : return this . handleUmaGrant ( params ) ;
7841 default : throw new BadRequestHttpError ( `Unsupported grant_type ${ params . grant_type } ` ) ;
7942 }
@@ -98,88 +61,4 @@ export class TokenRequestHandler extends HttpHandler {
9861 throw e ; // TODO: distinguish other errors
9962 }
10063 }
101-
102- protected async handlePatRequest ( params : DialogInput , authorization ?: string ) : Promise < HttpHandlerResponse < any > > {
103- const registration = await this . handlePreliminaryPatChecks ( params , authorization ) ;
104- // If there already is a stored token: reuse the ID
105- const matches = await this . storage . findIds ( PAT_STORAGE_TYPE , { registration : registration . id } ) ;
106- return this . generateToken ( registration , matches . length > 0 ? matches [ 0 ] : undefined ) ;
107- }
108-
109- protected async handleRefreshRequest ( params : DialogInput , authorization ?: string ) : Promise < HttpHandlerResponse < any > > {
110- if ( ! params . refresh_token ) {
111- throw new BadRequestHttpError ( `Missing refresh_token parameter` ) ;
112- }
113-
114- const pats = await this . storage . find ( PAT_STORAGE_TYPE , { refreshToken : params . refresh_token } ) ;
115- if ( pats . length === 0 ) {
116- throw new ForbiddenHttpError ( `Unknown refresh token ${ params . refresh_token } ` ) ;
117- }
118- const registration = await this . handlePreliminaryPatChecks ( params , authorization ) ;
119- if ( registration . id !== pats [ 0 ] . registration ) {
120- throw new ForbiddenHttpError ( `Wrong credentials for refresh token ${ params . refresh_token } ` ) ;
121- }
122-
123- return this . generateToken ( registration , pats [ 0 ] . id ) ;
124- }
125-
126- // Returns the UserId if there is a match, or throws an error
127- protected async handlePreliminaryPatChecks ( params : DialogInput , authorization ?: string ) :
128- Promise < TypeObject < typeof CLIENT_REGISTRATION_STORAGE_DESCRIPTION > > {
129- if ( typeof authorization !== 'string' ) {
130- throw new UnauthorizedHttpError ( ) ;
131- }
132- if ( params . scope !== 'uma_protection' ) {
133- throw new BadRequestHttpError ( `Expected scope 'uma_protection'` ) ;
134- }
135- if ( ! matchesAuthorizationScheme ( 'Basic' , authorization ) ) {
136- throw new BadRequestHttpError ( `Expected scheme 'Basic'` ) ;
137- }
138- const decoded = Buffer . from ( authorization . split ( ' ' ) [ 1 ] , 'base64' ) . toString ( 'utf8' ) ;
139- const [ id , secret ] = decoded . split ( ':' ) ;
140- const match = await this . storage . find ( CLIENT_REGISTRATION_STORAGE_TYPE ,
141- { clientId : decodeURIComponent ( id ) , clientSecret : decodeURIComponent ( secret ?? '' ) } ) ;
142- if ( match . length === 0 ) {
143- throw new ForbiddenHttpError ( ) ;
144- }
145- return match [ 0 ] ;
146- }
147-
148- protected async generateToken ( registration : TypeObject < typeof CLIENT_REGISTRATION_STORAGE_DESCRIPTION > , id ?: string ) :
149- Promise < HttpHandlerResponse < any > > {
150- const refresh_token = randomUUID ( ) ;
151- const expiration = Date . now ( ) + this . tokenExpiration * 1000 ;
152- const key = await this . keyGen . getPrivateKey ( ) ;
153- const jwk = await importJWK ( key , key . alg ) ;
154- const pat = await new SignJWT ( {
155- scope : 'uma_protection' ,
156- azp : registration . clientId ,
157- client_id : registration . clientId
158- } ) . setProtectedHeader ( { alg : key . alg , kid : key . kid } )
159- . setIssuedAt ( )
160- . setSubject ( registration . userId )
161- . setIssuer ( this . baseUrl )
162- . setAudience ( this . baseUrl )
163- . setExpirationTime ( Math . floor ( expiration / 1000 ) )
164- . setJti ( randomUUID ( ) )
165- . sign ( jwk ) ;
166-
167- const body = { pat, refreshToken : refresh_token , expiration, registration : registration . id } ;
168- if ( id ) {
169- await this . storage . set ( PAT_STORAGE_TYPE , { id, ...body } ) ;
170- } else {
171- await this . storage . create ( PAT_STORAGE_TYPE , body ) ;
172- }
173-
174- return {
175- status : 201 ,
176- body : {
177- access_token : pat ,
178- refresh_token,
179- token_type : 'Bearer' ,
180- expires_in : this . tokenExpiration ,
181- scope : 'uma_protection' ,
182- }
183- }
184- }
18564}
0 commit comments