Skip to content

Commit 7d207f1

Browse files
authored
Merge pull request #377 from ForgeRock/mock-api-updates2
Mock api updates2
2 parents b38b99a + cccb90d commit 7d207f1

14 files changed

Lines changed: 193 additions & 272 deletions

File tree

.changeset/config.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"baseBranch": "main",
1414
"updateInternalDependencies": "patch",
1515
"ignore": [
16+
"ping-javascript-sdk",
1617
"scratchpad",
1718
"@forgerock/pingone-scripts",
1819
"@forgerock/device-client",

.github/workflows/changesets-renovate.yml

Lines changed: 0 additions & 29 deletions
This file was deleted.

e2e/mock-api-v2/GEMINI.md

Whitespace-only changes.

e2e/mock-api-v2/src/addStepCookie.openapi.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export const addStepCookie = (spec: any) => {
99
const FLOW_TAGS = new Set(['Authorization', 'Capabilities']);
1010
const FLOW_PATH_MATCHERS = [
1111
/\/davinci\/authorize\b/,
12-
/\/davinci\/connections\/[^/]+\/capabilities\//,
12+
/\/davinci\/connections\/[^/]+\/capabilities/,
1313
];
1414

1515
const shouldAnnotate = (path: string, op: any) =>
@@ -32,6 +32,18 @@ export const addStepCookie = (spec: any) => {
3232
example: 2,
3333
});
3434
}
35+
const alreadyAcrValues = op.parameters.some(
36+
(p: any) => p && p.in === 'cookie' && p.name === 'acr_values',
37+
);
38+
if (!alreadyAcrValues && !op.tags?.includes('Authorization')) {
39+
op.parameters.push({
40+
name: 'acr_values',
41+
in: 'cookie',
42+
required: false,
43+
description: 'The acr_values that were used to initiate the authorization flow.',
44+
schema: { type: 'string' },
45+
});
46+
}
3547
};
3648

3749
const ensureSetCookie = (op: any, status: string) => {

e2e/mock-api-v2/src/handlers/authorize.handler.ts

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,26 @@
44
* This software may be modified and distributed under the terms
55
* of the MIT license. See the LICENSE file for details.
66
*/
7-
import { Effect } from 'effect';
8-
7+
import { Effect, pipe } from 'effect';
98
import { MockApi } from '../spec.js';
10-
import { HttpApiBuilder, HttpApiError } from '@effect/platform';
9+
import { HttpApiBuilder, HttpApiError, HttpServerResponse } from '@effect/platform';
1110
import { getFirstElementAndRespond } from '../services/mock-env-helpers/index.js';
1211

1312
const AuthorizeHandlerMock = HttpApiBuilder.group(MockApi, 'Authorization', (handlers) =>
1413
handlers.handle('authorize', ({ urlParams }) =>
1514
Effect.gen(function* () {
16-
/**
17-
* We expect an acr_value query parameter to be present in the request.
18-
* If it is not present, we return a 404 Not Found error.
19-
*/
2015
const acr_value = urlParams?.acr_values ?? '';
2116

22-
if (!acr_value) {
23-
return yield* Effect.fail(new HttpApiError.NotFound());
24-
}
17+
const body = yield* getFirstElementAndRespond(urlParams);
2518

26-
const response = yield* getFirstElementAndRespond(urlParams);
19+
const res = yield* pipe(
20+
HttpServerResponse.json(body),
21+
Effect.flatMap(HttpServerResponse.setCookie('acr_values', acr_value, { path: '/' })),
22+
Effect.catchTag('CookieError', () => Effect.fail(new HttpApiError.InternalServerError())),
23+
Effect.catchTag('HttpBodyError', () => Effect.fail(new HttpApiError.InternalServerError())),
24+
);
2725

28-
return response;
26+
return res;
2927
}).pipe(Effect.withSpan('DavinciAuthorize')),
3028
),
3129
);

e2e/mock-api-v2/src/handlers/capabilities.handler.ts

Lines changed: 42 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,11 @@
44
* This software may be modified and distributed under the terms
55
* of the MIT license. See the LICENSE file for details.
66
*/
7-
import { Console, Effect, pipe } from 'effect';
7+
import { Effect, pipe } from 'effect';
88
import { MockApi } from '../spec.js';
99
import {
1010
HttpApiBuilder,
1111
HttpApiError,
12-
HttpBody,
1312
HttpServerRequest,
1413
HttpServerResponse,
1514
} from '@effect/platform';
@@ -18,15 +17,13 @@ import { validator } from '../helpers/match.js';
1817
import { returnSuccessResponseRedirect } from '../responses/return-success-redirect.js';
1918

2019
const CapabilitiesHandlerMock = HttpApiBuilder.group(MockApi, 'Capabilities', (handlers) =>
21-
handlers.handle('capabilities', ({ urlParams, payload }) =>
20+
handlers.handle('capabilities', ({ payload }) =>
2221
Effect.gen(function* () {
23-
/**
24-
* We expect an acr_value query parameter to be present in the request.
25-
* If it is not present, we return a 404 Not Found error.
26-
*/
27-
const acr_value = urlParams?.acr_values ?? '';
28-
console.log('acr_value', acr_value);
22+
const req = yield* HttpServerRequest.HttpServerRequest;
23+
console.log('request cookies', req.cookies);
24+
const acr_value = req.cookies.acr_values ?? '';
2925

26+
console.log('acr_value', acr_value);
3027
if (!acr_value) {
3128
return yield* Effect.fail(new HttpApiError.NotFound());
3229
}
@@ -36,15 +33,13 @@ const CapabilitiesHandlerMock = HttpApiBuilder.group(MockApi, 'Capabilities', (h
3633
* If the cookie is not present, we return a 404 Not Found error.
3734
*/
3835

39-
const req = yield* HttpServerRequest.HttpServerRequest;
40-
4136
const stepIndexCookie = req.cookies['stepIndex'];
42-
console.log(req.cookies);
4337

4438
/**
4539
* If we are here with no step index that means we can't continue through a flow.
4640
* We should error
4741
*/
42+
console.log('step index cookie', stepIndexCookie);
4843
if (!stepIndexCookie) {
4944
console.log('no step index');
5045
return yield* Effect.fail(new HttpApiError.NotFound());
@@ -76,59 +71,49 @@ const CapabilitiesHandlerMock = HttpApiBuilder.group(MockApi, 'Capabilities', (h
7671
*/
7772
const steps = responseMap[acr_value];
7873

79-
/**
80-
* This may not be the best way to write this.
81-
* An alternative option would be for us to include the success response we want to return,
82-
* in the response map.
83-
*
84-
* then we can check if we are at the last step. if we are we write the cookie
85-
* and then we return the success response (last item in array)
86-
*
87-
* for now, this returns a default success response and writes cookies.
88-
*/
89-
if (stepIndex + 1 >= steps.length) {
74+
if (stepIndex + 1 === steps.length - 1) {
9075
/**
9176
* we need to return a success because we have not failed yet,
9277
* and we have no more steps to process.
9378
*/
94-
const body = yield* HttpBody.json(returnSuccessResponseRedirect).pipe(
95-
Effect.tap(Console.log(`here stepIndex: ${stepIndex}`)),
96-
/**
97-
* Decide on a better way to handle this error possibiltiy
98-
*/
99-
Effect.catchTag('HttpBodyError', () =>
100-
Effect.fail(
101-
new HttpApiError.HttpApiDecodeError({
102-
message: 'Failed to encode body',
103-
issues: [],
104-
}),
79+
const body = responseMap[stepIndex];
80+
81+
return yield* pipe(
82+
HttpServerResponse.json(body),
83+
Effect.flatMap((res) => HttpServerResponse.setCookie(res, 'ST', 'MockApiCookie123')),
84+
Effect.flatMap((res) =>
85+
HttpServerResponse.setCookie(
86+
res,
87+
'interactionId',
88+
returnSuccessResponseRedirect.interactionId,
89+
{
90+
httpOnly: true,
91+
secure: true,
92+
sameSite: 'strict',
93+
},
10594
),
10695
),
107-
);
108-
return pipe(
109-
HttpServerResponse.json(body),
110-
HttpServerResponse.setCookie('ST', 'MockApiCookie123'),
111-
HttpServerResponse.setCookie(
112-
'interactionId',
113-
returnSuccessResponseRedirect.interactionId,
114-
{
115-
httpOnly: true,
116-
secure: true,
117-
sameSite: 'strict',
118-
},
96+
Effect.flatMap((res) =>
97+
HttpServerResponse.setCookie(
98+
res,
99+
'interactionToken',
100+
returnSuccessResponseRedirect.interactionToken,
101+
{
102+
httpOnly: true,
103+
secure: true,
104+
sameSite: 'strict',
105+
},
106+
),
119107
),
120-
HttpServerResponse.setCookie(
121-
'interactionToken',
122-
returnSuccessResponseRedirect.interactionToken,
123-
{
124-
httpOnly: true,
125-
secure: true,
126-
sameSite: 'strict',
127-
},
108+
Effect.flatMap((res) => HttpServerResponse.removeCookie(res, 'stepIndex')),
109+
Effect.flatMap((res) => HttpServerResponse.setStatus(res, 200)),
110+
Effect.flatMap((res) =>
111+
HttpServerResponse.setHeader(res, 'Content-Type', 'application/json'),
112+
),
113+
Effect.catchTag('CookieError', () => Effect.fail(new HttpApiError.InternalServerError())),
114+
Effect.catchTag('HttpBodyError', () =>
115+
Effect.fail(new HttpApiError.InternalServerError()),
128116
),
129-
HttpServerResponse.removeCookie('stepIndex'),
130-
HttpServerResponse.setStatus(200),
131-
HttpServerResponse.setHeader('Content-Type', 'application/json'),
132117
);
133118
}
134119

e2e/mock-api-v2/src/handlers/open-id-configuration.handler.ts

Lines changed: 87 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,96 @@
66
*/
77
import { Effect } from 'effect';
88
import { MockApi } from '../spec.js';
9-
import { openidConfigurationResponse } from '../responses/open-id-configuration.js';
109
import { HttpApiBuilder } from '@effect/platform';
10+
import { HttpServerRequest } from '@effect/platform/HttpServerRequest';
1111

12-
/**
13-
* TODO: This needs to make a request for an openid configuration in a LIVE environment
14-
* The proper way is to probably create a LIVE (effect convention) route, that handles this
15-
* then the LIVE app is provided the HttpClient needed
16-
*
17-
*
18-
*/
1912
const OpenidConfigMock = HttpApiBuilder.group(MockApi, 'OpenIDConfig', (handlers) =>
20-
handlers.handle(
21-
'openid',
22-
Effect.fn('OpenId')(function* () {
23-
const value = yield* Effect.succeed(openidConfigurationResponse);
24-
return value;
13+
handlers.handle('openid', ({ path: { envid } }) =>
14+
Effect.gen(function* () {
15+
const request = yield* HttpServerRequest;
16+
const url = new URL(request.url);
17+
const issuer = `${url.protocol}//${url.host}/${envid}/as`;
18+
return {
19+
issuer,
20+
authorization_endpoint: `${issuer}/authorize`,
21+
pushed_authorization_request_endpoint: `${issuer}/par`,
22+
token_endpoint: `${issuer}/token`,
23+
userinfo_endpoint: `${issuer}/userinfo`,
24+
jwks_uri: `${issuer}/jwks`,
25+
end_session_endpoint: `${issuer}/signoff`,
26+
check_session_iframe: `${issuer}/checksession`,
27+
introspection_endpoint: `${issuer}/introspect`,
28+
revocation_endpoint: `${issuer}/revoke`,
29+
device_authorization_endpoint: `${issuer}/device_authorization`,
30+
claims_parameter_supported: false,
31+
request_parameter_supported: true,
32+
request_uri_parameter_supported: false,
33+
require_pushed_authorization_requests: false,
34+
scopes_supported: ['openid', 'profile', 'email', 'address', 'phone'],
35+
response_types_supported: [
36+
'code',
37+
'id_token',
38+
'token id_token',
39+
'code id_token',
40+
'code token',
41+
'code token id_token',
42+
],
43+
response_modes_supported: ['pi.flow', 'query', 'fragment', 'form_post'],
44+
grant_types_supported: [
45+
'authorization_code',
46+
'implicit',
47+
'client_credentials',
48+
'refresh_token',
49+
'urn:ietf:params:oauth:grant-type:device_code',
50+
],
51+
subject_types_supported: ['public'],
52+
id_token_signing_alg_values_supported: ['RS256'],
53+
userinfo_signing_alg_values_supported: ['none'],
54+
request_object_signing_alg_values_supported: [
55+
'none',
56+
'HS256',
57+
'HS384',
58+
'HS512',
59+
'RS256',
60+
'RS384',
61+
'RS512',
62+
],
63+
token_endpoint_auth_methods_supported: [
64+
'client_secret_basic',
65+
'client_secret_post',
66+
'client_secret_jwt',
67+
'private_key_jwt',
68+
],
69+
token_endpoint_auth_signing_alg_values_supported: [
70+
'HS256',
71+
'HS384',
72+
'HS512',
73+
'RS256',
74+
'RS384',
75+
'RS512',
76+
],
77+
claim_types_supported: ['normal'],
78+
claims_supported: [
79+
'sub',
80+
'iss',
81+
'auth_time',
82+
'acr',
83+
'name',
84+
'given_name',
85+
'family_name',
86+
'middle_name',
87+
'preferred_username',
88+
'profile',
89+
'picture',
90+
'zoneinfo',
91+
'phone_number',
92+
'updated_at',
93+
'address',
94+
'email',
95+
'locale',
96+
],
97+
code_challenge_methods_supported: ['plain', 'S256'],
98+
};
2599
}),
26100
),
27101
);

e2e/mock-api-v2/src/handlers/userinfo.handler.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,18 @@
77
import { Effect } from 'effect';
88
import { MockApi } from '../spec.js';
99
import { UserInfo } from '../services/userinfo.service.js';
10-
import { HttpApiBuilder } from '@effect/platform';
10+
import { HttpApiBuilder, HttpApiError } from '@effect/platform';
1111
import { BearerToken } from '../middleware/Authorization.js';
1212

1313
const UserInfoMockHandler = HttpApiBuilder.group(MockApi, 'ProtectedRequests', (handlers) =>
1414
handlers.handle('UserInfo', () =>
1515
Effect.gen(function* () {
1616
const authToken = yield* BearerToken;
17+
18+
if (!authToken) {
19+
return yield* Effect.fail(new HttpApiError.Unauthorized());
20+
}
21+
1722
const { getUserInfo } = yield* UserInfo;
1823

1924
const response = yield* getUserInfo(authToken);

e2e/mock-api-v2/src/main.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ const ServerMock = HttpApiBuilder.serve(HttpMiddleware.logger).pipe(
6666

6767
Layer.provide(NodeSdkLive),
6868
HttpServer.withLogAddress,
69-
Layer.provide(NodeHttpServer.layer(createServer, { port: 9443 })),
69+
Layer.provide(NodeHttpServer.layer(createServer, { port: 9443, host: 'localhost' })),
7070
);
7171

7272
Layer.launch(ServerMock).pipe(NodeRuntime.runMain);

0 commit comments

Comments
 (0)