@@ -8,19 +8,17 @@ import { CustomLogger } from '@forgerock/sdk-logger';
88import { Micro } from 'effect' ;
99
1010import {
11- authorizeFetchµ ,
1211 createAuthorizeUrlµ ,
13- authorizeIframeµ ,
1412 buildAuthorizeOptionsµ ,
1513 createAuthorizeErrorµ ,
1614} from './authorize.request.utils.js' ;
1715
1816import type { GetAuthorizationUrlOptions , WellKnownResponse } from '@forgerock/sdk-types' ;
17+
18+ import type { AuthorizationError , AuthorizationSuccess } from './authorize.request.types.js' ;
19+ import type { createClientStore } from './client.store.utils.js' ;
1920import type { OidcConfig } from './config.types.js' ;
20- import type {
21- AuthorizeErrorResponse ,
22- AuthorizeSuccessResponse ,
23- } from './authorize.request.types.js' ;
21+ import { oidcApi } from './oidc.api.js' ;
2422
2523/**
2624 * @function authorizeµ
@@ -29,67 +27,153 @@ import type {
2927 * @param {OidcConfig } config - The OIDC client configuration.
3028 * @param {CustomLogger } log - The logger instance for logging debug information.
3129 * @param {GetAuthorizationUrlOptions } options - Optional parameters for the authorization request.
32- * @returns {Micro.Micro<AuthorizeSuccessResponse, AuthorizeErrorResponse , never> } - A micro effect that resolves to the authorization response.
30+ * @returns {Micro.Micro<AuthorizationSuccess, AuthorizationError , never> } - A micro effect that resolves to the authorization response.
3331 */
3432export function authorizeµ (
3533 wellknown : WellKnownResponse ,
3634 config : OidcConfig ,
3735 log : CustomLogger ,
36+ store : ReturnType < typeof createClientStore > ,
3837 options ?: GetAuthorizationUrlOptions ,
3938) {
4039 return buildAuthorizeOptionsµ ( wellknown , config , options ) . pipe (
4140 Micro . flatMap ( ( [ url , config , options ] ) => createAuthorizeUrlµ ( url , config , options ) ) ,
4241 Micro . tap ( ( url ) => log . debug ( 'Authorize URL created' , url ) ) ,
4342 Micro . tapError ( ( url ) => Micro . sync ( ( ) => log . error ( 'Error creating authorize URL' , url ) ) ) ,
44- Micro . flatMap ( ( [ url , config , options ] ) => {
45- if ( options . responseMode === 'pi.flow' ) {
46- /**
47- * If we support the pi.flow field, this means we are using a PingOne server.
48- * PingOne servers do not support redirection through iframes because they
49- * set iframe's to DENY.
50- *
51- * We do not use RTK Query for this because we don't want caching, or store
52- * updates, and want the request to be made similar to the iframe method below.
53- *
54- * This returns a Micro that resolves to the parsed response JSON.
55- */
56- return authorizeFetchµ ( url ) . pipe (
57- Micro . flatMap (
58- ( response ) : Micro . Micro < AuthorizeSuccessResponse , AuthorizeErrorResponse , never > => {
59- if ( 'code' in response ) {
60- log . debug ( 'Received code in response' , response ) ;
61- return Micro . succeed ( response ) ;
62- }
63- log . error ( 'Error in authorize response' , response ) ;
64- // For redirection, we need to remove `pi.flow` from the options
65- const redirectOptions = options ;
66- delete redirectOptions . responseMode ;
67- return createAuthorizeErrorµ ( response , wellknown , config , options ) ;
68- } ,
69- ) ,
70- ) ;
71- } else {
72- /**
73- * If the response mode is not pi.flow, then we are likely using a traditional
74- * redirect based server supporting iframes. An example would be PingAM.
75- *
76- * This returns a Micro that's either the success URL parameters or error URL
77- * parameters.
78- */
79- return authorizeIframeµ ( url , config ) . pipe (
80- Micro . flatMap (
81- ( response ) : Micro . Micro < AuthorizeSuccessResponse , AuthorizeErrorResponse , never > => {
82- if ( 'code' in response && 'state' in response ) {
83- log . debug ( 'Received authorization code' , response ) ;
84- return Micro . succeed ( response as unknown as AuthorizeSuccessResponse ) ;
85- }
86- log . error ( 'Error in authorize response' , response ) ;
87- const errorResponse = response as unknown as AuthorizeErrorResponse ;
88- return createAuthorizeErrorµ ( errorResponse , wellknown , config , options ) ;
89- } ,
90- ) ,
91- ) ;
92- }
93- } ) ,
43+ Micro . flatMap (
44+ ( [ url , options ] ) : Micro . Micro < AuthorizationSuccess , AuthorizationError , never > => {
45+ if ( options . responseMode === 'pi.flow' ) {
46+ /**
47+ * If we support the pi.flow field, this means we are using a PingOne server.
48+ * PingOne servers do not support redirection through iframes because they
49+ * set iframe's to DENY.
50+ *
51+ * We do not use RTK Query for this because we don't want caching, or store
52+ * updates, and want the request to be made similar to the iframe method below.
53+ *
54+ * This returns a Micro that resolves to the parsed response JSON.
55+ */
56+ return Micro . promise ( ( ) =>
57+ store . dispatch ( oidcApi . endpoints . authorizeFetch . initiate ( { url } ) ) ,
58+ ) . pipe (
59+ Micro . flatMap (
60+ ( { error, data } ) : Micro . Micro < AuthorizationSuccess , AuthorizationError , never > => {
61+ if ( error ) {
62+ // Check for serialized error
63+ if ( ! ( 'status' in error ) ) {
64+ // This is a network or fetch error, so return it as-is
65+ return Micro . fail ( {
66+ error : error . code || 'Unknown_Error' ,
67+ error_description :
68+ error . message || 'An unknown error occurred during authorization' ,
69+ type : 'unknown_error' ,
70+ } ) ;
71+ }
72+
73+ // If there is no data, this is an unknown error
74+ if ( ! ( 'data' in error ) ) {
75+ return Micro . fail ( {
76+ error : 'Unknown_Error' ,
77+ error_description : 'An unknown error occurred during authorization' ,
78+ type : 'unknown_error' ,
79+ } ) ;
80+ }
81+
82+ const errorDetails = error . data as AuthorizationError ;
83+
84+ // If the error is a configuration issue, return it as-is
85+ if ( 'statusText' in error && error . statusText === 'CONFIGURATION_ERROR' ) {
86+ return Micro . fail ( errorDetails ) ;
87+ }
88+
89+ // If the error is not a configuration issue, we build a new Authorize URL
90+ // For redirection, we need to remove `pi.flow` from the options
91+ const redirectOptions = options ;
92+ delete redirectOptions . responseMode ;
93+
94+ // Create an error with a new Authorize URL
95+ return createAuthorizeErrorµ ( errorDetails , wellknown , options ) ;
96+ }
97+
98+ log . debug ( 'Received success response' , data ) ;
99+
100+ if ( data . authorizeResponse ) {
101+ // Authorization was successful
102+ return Micro . succeed ( data . authorizeResponse ) ;
103+ } else {
104+ // This should never be reached, but just in case
105+ return Micro . fail ( {
106+ error : 'Unknown_Error' ,
107+ error_description : 'Response schema was not recognized' ,
108+ type : 'unknown_error' ,
109+ } ) ;
110+ }
111+ } ,
112+ ) ,
113+ ) ;
114+ } else {
115+ /**
116+ * If the response mode is not pi.flow, then we are likely using a traditional
117+ * redirect based server supporting iframes. An example would be PingAM.
118+ *
119+ * This returns a Micro that's either the success URL parameters or error URL
120+ * parameters.
121+ */
122+ return Micro . promise ( ( ) =>
123+ store . dispatch ( oidcApi . endpoints . authorizeIframe . initiate ( { url } ) ) ,
124+ ) . pipe (
125+ Micro . flatMap (
126+ ( { error, data } ) : Micro . Micro < AuthorizationSuccess , AuthorizationError , never > => {
127+ if ( error ) {
128+ // Check for serialized error
129+ if ( ! ( 'status' in error ) ) {
130+ // This is a network or fetch error, so return it as-is
131+ return Micro . fail ( {
132+ error : error . code || 'Unknown_Error' ,
133+ error_description :
134+ error . message || 'An unknown error occurred during authorization' ,
135+ type : 'unknown_error' ,
136+ } ) ;
137+ }
138+
139+ // If there is no data, this is an unknown error
140+ if ( ! ( 'data' in error ) ) {
141+ return Micro . fail ( {
142+ error : 'Unknown_Error' ,
143+ error_description : 'An unknown error occurred during authorization' ,
144+ type : 'unknown_error' ,
145+ } ) ;
146+ }
147+
148+ const errorDetails = error . data as AuthorizationError ;
149+
150+ // If the error is a configuration issue, return it as-is
151+ if ( 'statusText' in error && error . statusText === 'CONFIGURATION_ERROR' ) {
152+ return Micro . fail ( errorDetails ) ;
153+ }
154+
155+ // This is an expected error, so combine error with a new Authorize URL
156+ return createAuthorizeErrorµ ( errorDetails , wellknown , options ) ;
157+ }
158+
159+ log . debug ( 'Received success response' , data ) ;
160+
161+ if ( data ) {
162+ // Authorization was successful
163+ return Micro . succeed ( data ) ;
164+ } else {
165+ // This should never be reached, but just in case
166+ return Micro . fail ( {
167+ error : 'Unknown_Error' ,
168+ error_description : 'Redirect parameters was not recognized' ,
169+ type : 'unknown_error' ,
170+ } ) ;
171+ }
172+ } ,
173+ ) ,
174+ ) ;
175+ }
176+ } ,
177+ ) ,
94178 ) ;
95179}
0 commit comments