-
Notifications
You must be signed in to change notification settings - Fork 3
chore: update-routes #365
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
chore: update-routes #365
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| /* | ||
| * Copyright (c) 2025 Ping Identity Corporation. All rights reserved. | ||
| * | ||
| * This software may be modified and distributed under the terms | ||
| * of the MIT license. See the LICENSE file for details. | ||
| */ | ||
|
|
||
| export const addStepCookie = (spec: any) => { | ||
| const FLOW_TAGS = new Set(['Authorization', 'Capabilities']); | ||
| const FLOW_PATH_MATCHERS = [ | ||
| /\/davinci\/authorize\b/, | ||
| /\/davinci\/connections\/[^/]+\/capabilities\//, | ||
| ]; | ||
|
|
||
| const shouldAnnotate = (path: string, op: any) => | ||
| (Array.isArray(op?.tags) && op.tags.some((t: string) => FLOW_TAGS.has(t))) || | ||
| FLOW_PATH_MATCHERS.some((rx) => rx.test(path)); | ||
|
|
||
| const addCookieParam = (op: any) => { | ||
| op.parameters ||= []; | ||
| const already = op.parameters.some( | ||
| (p: any) => p && p.in === 'cookie' && p.name === 'stepIndex', | ||
| ); | ||
| if (!already) { | ||
| op.parameters.push({ | ||
| name: 'stepIndex', | ||
| in: 'cookie', | ||
| required: false, | ||
| description: | ||
| 'Current flow step. Server initializes on first request and increments thereafter.', | ||
| schema: { type: 'integer', minimum: 0 }, | ||
| example: 2, | ||
| }); | ||
| } | ||
| }; | ||
|
|
||
| const ensureSetCookie = (op: any, status: string) => { | ||
| op.responses ||= {}; | ||
| const resp = (op.responses[status] ||= { description: 'Success' }); | ||
| resp.headers ||= {}; | ||
| if (!resp.headers['Set-Cookie']) { | ||
| resp.headers['Set-Cookie'] = { | ||
| description: | ||
| 'Updated step cookie (e.g., `stepIndex=3; Path=/; HttpOnly; Secure; SameSite=Lax`). ' + | ||
| 'May be removed on completion.', | ||
| schema: { type: 'string' }, | ||
| example: 'stepIndex=3; Path=/; HttpOnly; Secure; SameSite=Lax', | ||
| }; | ||
| } | ||
| }; | ||
|
|
||
| for (const [path, methods] of Object.entries(spec.paths ?? {})) { | ||
| for (const [, op] of Object.entries<any>(methods as any)) { | ||
| if (!op || typeof op !== 'object') continue; | ||
| if (!shouldAnnotate(path, op)) continue; | ||
| addCookieParam(op); | ||
| // Add for common success statuses you use | ||
| ensureSetCookie(op, '200'); | ||
| ensureSetCookie(op, '302'); | ||
| } | ||
| } | ||
|
|
||
| return spec; | ||
| }; | ||
|
|
||
| export default addStepCookie; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,148 @@ | ||
| /** | ||
| * Copyright (c) 2025 Ping Identity Corporation. All rights reserved. | ||
| * | ||
| * This software may be modified and distributed under the terms | ||
| * of the MIT license. See the LICENSE file for details. | ||
| */ | ||
| import { Console, Effect, pipe } from 'effect'; | ||
| import { MockApi } from '../spec.js'; | ||
| import { | ||
| HttpApiBuilder, | ||
| HttpApiError, | ||
| HttpBody, | ||
| HttpServerRequest, | ||
| HttpServerResponse, | ||
| } from '@effect/platform'; | ||
| import { responseMap } from '../responses/index.js'; | ||
| import { validator } from '../helpers/match.js'; | ||
| import { returnSuccessResponseRedirect } from '../responses/return-success-redirect.js'; | ||
|
|
||
| const CapabilitiesHandlerMock = HttpApiBuilder.group(MockApi, 'Capabilities', (handlers) => | ||
| handlers.handle('capabilities', ({ urlParams, payload }) => | ||
| Effect.gen(function* () { | ||
| /** | ||
| * We expect an acr_value query parameter to be present in the request. | ||
| * If it is not present, we return a 404 Not Found error. | ||
| */ | ||
| const acr_value = urlParams?.acr_values ?? ''; | ||
| console.log('acr_value', acr_value); | ||
|
|
||
| if (!acr_value) { | ||
| return yield* Effect.fail(new HttpApiError.NotFound()); | ||
| } | ||
|
Comment on lines
+27
to
+32
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are ACR Values used within this route? I thought ACR was an
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I need to know what flow to pull from, unless we keep the state in another cookie, i have no way of mapping the current request to the responseMap.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think a cookie is a good idea, but let's not worry about it right now. |
||
|
|
||
| /** | ||
| * We need a step index cookie to determine which step of the authentication process we are on. | ||
| * If the cookie is not present, we return a 404 Not Found error. | ||
| */ | ||
|
|
||
| const req = yield* HttpServerRequest.HttpServerRequest; | ||
|
|
||
| const stepIndexCookie = req.cookies['stepIndex']; | ||
| console.log(req.cookies); | ||
|
|
||
| /** | ||
| * If we are here with no step index that means we can't continue through a flow. | ||
| * We should error | ||
| */ | ||
| if (!stepIndexCookie) { | ||
| console.log('no step index'); | ||
| return yield* Effect.fail(new HttpApiError.NotFound()); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this error be more detailed, rather than just 404? |
||
| } | ||
|
|
||
| const stepIndex = parseInt(stepIndexCookie); | ||
|
|
||
| /** | ||
| * If we have no step index, we should error or if its an invalid number | ||
| */ | ||
|
|
||
| if (isNaN(stepIndex) || stepIndex < 0) { | ||
| return yield* Effect.fail(new HttpApiError.NotFound()); | ||
| } | ||
|
|
||
| /** | ||
| * Match the body against validators now | ||
| * if the body has no match, we are defaulting to a successful response. | ||
| */ | ||
| const result = yield* validator(payload); | ||
|
|
||
| if (result === false) { | ||
| return yield* Effect.fail(new HttpApiError.Unauthorized()); | ||
| } | ||
|
|
||
| /** | ||
| * We use the step index to find the next step in the response map. | ||
| * If the step index is out of bounds, we return a 404 Not Found error. | ||
| */ | ||
| const steps = responseMap[acr_value]; | ||
|
|
||
| /** | ||
| * This may not be the best way to write this. | ||
| * An alternative option would be for us to include the success response we want to return, | ||
| * in the response map. | ||
| * | ||
| * then we can check if we are at the last step. if we are we write the cookie | ||
| * and then we return the success response (last item in array) | ||
| * | ||
| * for now, this returns a default success response and writes cookies. | ||
| */ | ||
| if (stepIndex + 1 >= steps.length) { | ||
| /** | ||
| * we need to return a success because we have not failed yet, | ||
| * and we have no more steps to process. | ||
| */ | ||
| const body = yield* HttpBody.json(returnSuccessResponseRedirect).pipe( | ||
| Effect.tap(Console.log(`here stepIndex: ${stepIndex}`)), | ||
| /** | ||
| * Decide on a better way to handle this error possibiltiy | ||
| */ | ||
| Effect.catchTag('HttpBodyError', () => | ||
| Effect.fail( | ||
| new HttpApiError.HttpApiDecodeError({ | ||
| message: 'Failed to encode body', | ||
| issues: [], | ||
| }), | ||
| ), | ||
| ), | ||
| ); | ||
| return pipe( | ||
| HttpServerResponse.json(body), | ||
| HttpServerResponse.setCookie('ST', 'MockApiCookie123'), | ||
| HttpServerResponse.setCookie( | ||
| 'interactionId', | ||
| returnSuccessResponseRedirect.interactionId, | ||
| { | ||
| httpOnly: true, | ||
| secure: true, | ||
| sameSite: 'strict', | ||
| }, | ||
| ), | ||
| HttpServerResponse.setCookie( | ||
| 'interactionToken', | ||
| returnSuccessResponseRedirect.interactionToken, | ||
| { | ||
| httpOnly: true, | ||
| secure: true, | ||
| sameSite: 'strict', | ||
| }, | ||
| ), | ||
| HttpServerResponse.removeCookie('stepIndex'), | ||
| HttpServerResponse.setStatus(200), | ||
| HttpServerResponse.setHeader('Content-Type', 'application/json'), | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * The stepIndex middleware is used to auto-increment the step index | ||
| * based on the request type. If the step index is out of bounds, | ||
| * we return a 404 Not Found error. so we won't increment it, but we check for the next step | ||
| * in the flow. | ||
| */ | ||
| const nextStep = steps[stepIndex + 1]; | ||
|
|
||
| return nextStep; | ||
| }).pipe(Effect.withSpan('Capabilities Handler Mock')), | ||
| ), | ||
| ); | ||
|
|
||
| export { CapabilitiesHandlerMock }; | ||
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are we requiring the use of
acr_values?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
because i have no way of directing the code to a given flow, unless I provide a default.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could have a fallback, but I see what you're saying. We can address this later.