@@ -13,6 +13,7 @@ import { SpcpOidcBaseClientCache } from '../spcp.oidc.client.cache'
1313import {
1414 CreateAuthorisationUrlError ,
1515 CreateJwtError ,
16+ CreateParRequestError ,
1617 ExchangeAuthTokenError ,
1718 GetDecryptionKeyError ,
1819 GetSigningKeyError ,
@@ -32,9 +33,11 @@ import {
3233import {
3334 CP_OIDC_NDI_DISCOVERY_ENDPOINT ,
3435 CP_OIDC_NDI_JWKS_ENDPOINT ,
36+ CP_OIDC_NDI_PAR_ENDPOINT ,
3537 CP_OIDC_RP_CLIENT_ID ,
3638 CP_OIDC_RP_REDIRECT_URL ,
3739 cpOidcClientConfig ,
40+ cpOidcClientConfigWithPar ,
3841 SP_OIDC_NDI_DISCOVERY_ENDPOINT ,
3942 SP_OIDC_NDI_JWKS_ENDPOINT ,
4043 SP_OIDC_RP_CLIENT_ID ,
@@ -2203,6 +2206,215 @@ describe('CpOidcClient', () => {
22032206 } )
22042207 } )
22052208
2209+ describe ( 'createAuthorisationUrlWithPar' , ( ) => {
2210+ it ( 'should throw CreateAuthorisationUrlError if state parameter is empty' , async ( ) => {
2211+ // Arrange
2212+ jest
2213+ . spyOn ( SpcpOidcBaseClientCache . prototype , 'refresh' )
2214+ . mockResolvedValueOnce ( 'ok' as unknown as Refresh )
2215+
2216+ const MOCK_EMPTY_STATE = ''
2217+ const MOCK_ESRVCID = 'esrvcId'
2218+
2219+ // Act
2220+
2221+ const cpOidcClient = new CpOidcClient ( cpOidcClientConfigWithPar )
2222+ const tryCreateUrl = cpOidcClient . createAuthorisationUrlWithPar (
2223+ MOCK_EMPTY_STATE ,
2224+ MOCK_ESRVCID ,
2225+ )
2226+
2227+ // Assert
2228+ await expect ( tryCreateUrl ) . rejects . toThrow ( CreateAuthorisationUrlError )
2229+ await expect ( tryCreateUrl ) . rejects . toThrow ( 'Empty state' )
2230+ } )
2231+
2232+ it ( 'should throw CreateAuthorisationUrlError if esrvcId parameter is empty' , async ( ) => {
2233+ // Arrange
2234+ jest
2235+ . spyOn ( SpcpOidcBaseClientCache . prototype , 'refresh' )
2236+ . mockResolvedValueOnce ( 'ok' as unknown as Refresh )
2237+
2238+ const MOCK_STATE = 'state'
2239+ const MOCK_EMPTY_ESRVCID = ''
2240+
2241+ // Act
2242+
2243+ const cpOidcClient = new CpOidcClient ( cpOidcClientConfigWithPar )
2244+ const tryCreateUrl = cpOidcClient . createAuthorisationUrlWithPar (
2245+ MOCK_STATE ,
2246+ MOCK_EMPTY_ESRVCID ,
2247+ )
2248+
2249+ // Assert
2250+ await expect ( tryCreateUrl ) . rejects . toThrow ( CreateAuthorisationUrlError )
2251+ await expect ( tryCreateUrl ) . rejects . toThrow ( 'Empty esrvcId' )
2252+ } )
2253+
2254+ it ( 'should throw CreateParRequestError if PAR endpoint is not configured' , async ( ) => {
2255+ // Arrange
2256+ jest
2257+ . spyOn ( SpcpOidcBaseClientCache . prototype , 'refresh' )
2258+ . mockResolvedValueOnce ( 'ok' as unknown as Refresh )
2259+
2260+ const MOCK_STATE = 'state'
2261+ const MOCK_ESRVCID = 'esrvcId'
2262+
2263+ // Act
2264+ // Use the config without PAR endpoint
2265+ const cpOidcClient = new CpOidcClient ( cpOidcClientConfig )
2266+ const tryCreateUrl = cpOidcClient . createAuthorisationUrlWithPar (
2267+ MOCK_STATE ,
2268+ MOCK_ESRVCID ,
2269+ )
2270+
2271+ // Assert
2272+ await expect ( tryCreateUrl ) . rejects . toThrow ( CreateParRequestError )
2273+ await expect ( tryCreateUrl ) . rejects . toThrow ( 'PAR endpoint not configured' )
2274+ } )
2275+
2276+ it ( 'should correctly POST to PAR endpoint and return authorisation URL with request_uri' , async ( ) => {
2277+ // Arrange
2278+ jest
2279+ . spyOn ( SpcpOidcBaseClientCache . prototype , 'refresh' )
2280+ . mockResolvedValueOnce ( 'ok' as unknown as Refresh )
2281+
2282+ const MOCK_STATE = 'state'
2283+ const MOCK_ESRVCID = 'esrvcId'
2284+ const MOCK_REQUEST_URI = 'urn:ietf:params:oauth:request_uri:12345'
2285+ const MOCK_AUTH_ENDPOINT = 'https://corppass.example.com/authorize'
2286+
2287+ jest
2288+ . spyOn ( CpOidcClient . prototype , 'getBaseClientFromCache' )
2289+ . mockResolvedValueOnce ( {
2290+ issuer : {
2291+ metadata : {
2292+ authorization_endpoint : MOCK_AUTH_ENDPOINT ,
2293+ issuer : 'https://corppass.example.com' ,
2294+ } ,
2295+ } ,
2296+ } as unknown as BaseClient )
2297+
2298+ jest
2299+ . spyOn ( CpOidcClient . prototype , 'createJWT' )
2300+ . mockResolvedValueOnce ( 'mockJwt' )
2301+
2302+ const axiosSpy = jest . spyOn ( axios , 'post' ) . mockResolvedValueOnce ( {
2303+ data : {
2304+ request_uri : MOCK_REQUEST_URI ,
2305+ expires_in : 90 ,
2306+ } ,
2307+ } )
2308+
2309+ // Act
2310+ const cpOidcClient = new CpOidcClient ( cpOidcClientConfigWithPar )
2311+ const result = await cpOidcClient . createAuthorisationUrlWithPar (
2312+ MOCK_STATE ,
2313+ MOCK_ESRVCID ,
2314+ )
2315+
2316+ // Assert
2317+ expect ( axiosSpy ) . toHaveBeenCalledOnce ( )
2318+ expect ( axiosSpy ) . toHaveBeenCalledWith (
2319+ CP_OIDC_NDI_PAR_ENDPOINT ,
2320+ expect . stringContaining ( 'esrvcID' ) ,
2321+ expect . objectContaining ( {
2322+ headers : {
2323+ 'Content-Type' : 'application/x-www-form-urlencoded' ,
2324+ } ,
2325+ } ) ,
2326+ )
2327+ expect ( result ) . toContain ( MOCK_AUTH_ENDPOINT )
2328+ expect ( result ) . toContain ( 'request_uri=' )
2329+ expect ( result ) . toContain ( encodeURIComponent ( MOCK_REQUEST_URI ) )
2330+ } )
2331+
2332+ it ( 'should throw CreateParRequestError if PAR response is missing request_uri' , async ( ) => {
2333+ // Arrange
2334+ jest
2335+ . spyOn ( SpcpOidcBaseClientCache . prototype , 'refresh' )
2336+ . mockResolvedValueOnce ( 'ok' as unknown as Refresh )
2337+
2338+ const MOCK_STATE = 'state'
2339+ const MOCK_ESRVCID = 'esrvcId'
2340+
2341+ jest
2342+ . spyOn ( CpOidcClient . prototype , 'getBaseClientFromCache' )
2343+ . mockResolvedValueOnce ( {
2344+ issuer : {
2345+ metadata : {
2346+ authorization_endpoint : 'https://corppass.example.com/authorize' ,
2347+ issuer : 'https://corppass.example.com' ,
2348+ } ,
2349+ } ,
2350+ } as unknown as BaseClient )
2351+
2352+ jest
2353+ . spyOn ( CpOidcClient . prototype , 'createJWT' )
2354+ . mockResolvedValueOnce ( 'mockJwt' )
2355+
2356+ jest . spyOn ( axios , 'post' ) . mockResolvedValueOnce ( {
2357+ data : {
2358+ expires_in : 90 ,
2359+ // Missing request_uri
2360+ } ,
2361+ } )
2362+
2363+ // Act
2364+ const cpOidcClient = new CpOidcClient ( cpOidcClientConfigWithPar )
2365+ const tryCreateUrl = cpOidcClient . createAuthorisationUrlWithPar (
2366+ MOCK_STATE ,
2367+ MOCK_ESRVCID ,
2368+ )
2369+
2370+ // Assert
2371+ await expect ( tryCreateUrl ) . rejects . toThrow ( CreateParRequestError )
2372+ await expect ( tryCreateUrl ) . rejects . toThrow (
2373+ 'PAR response missing request_uri' ,
2374+ )
2375+ } )
2376+
2377+ it ( 'should throw CreateParRequestError if PAR request fails' , async ( ) => {
2378+ // Arrange
2379+ jest
2380+ . spyOn ( SpcpOidcBaseClientCache . prototype , 'refresh' )
2381+ . mockResolvedValueOnce ( 'ok' as unknown as Refresh )
2382+
2383+ const MOCK_STATE = 'state'
2384+ const MOCK_ESRVCID = 'esrvcId'
2385+
2386+ jest
2387+ . spyOn ( CpOidcClient . prototype , 'getBaseClientFromCache' )
2388+ . mockResolvedValueOnce ( {
2389+ issuer : {
2390+ metadata : {
2391+ authorization_endpoint : 'https://corppass.example.com/authorize' ,
2392+ issuer : 'https://corppass.example.com' ,
2393+ } ,
2394+ } ,
2395+ } as unknown as BaseClient )
2396+
2397+ jest
2398+ . spyOn ( CpOidcClient . prototype , 'createJWT' )
2399+ . mockResolvedValueOnce ( 'mockJwt' )
2400+
2401+ jest
2402+ . spyOn ( axios , 'post' )
2403+ . mockRejectedValueOnce ( new Error ( 'Network error' ) )
2404+
2405+ // Act
2406+ const cpOidcClient = new CpOidcClient ( cpOidcClientConfigWithPar )
2407+ const tryCreateUrl = cpOidcClient . createAuthorisationUrlWithPar (
2408+ MOCK_STATE ,
2409+ MOCK_ESRVCID ,
2410+ )
2411+
2412+ // Assert
2413+ await expect ( tryCreateUrl ) . rejects . toThrow ( CreateParRequestError )
2414+ await expect ( tryCreateUrl ) . rejects . toThrow ( 'PAR request failed' )
2415+ } )
2416+ } )
2417+
22062418 describe ( 'getDecryptionKey' , ( ) => {
22072419 it ( 'should return GetDecryptionKeyError if jwe is empty' , ( ) => {
22082420 // Arrange
0 commit comments