|
1 | | -### Mock Api |
| 1 | +A mock API server for simulating Ping Identity and OpenID Connect flows, built using [Effect](https://effect.website/) and [@effect/platform](https://github.com/Effect-TS/platform). This service is designed for end-to-end testing and development, providing realistic responses for authentication and authorization scenarios. |
2 | 2 |
|
3 | | -## Docs |
| 3 | +## Features |
4 | 4 |
|
5 | | -you can run the server and visit `http://localhost:9443/docs#` to visit the swagger docs |
6 | | -The swagger docs are automatically created (with effect-http) by way of the schemas that are defined in the [spec file]('./src/spec.ts') |
| 5 | +- Implements endpoints for: |
| 6 | + - Healthcheck |
| 7 | + - OpenID Connect Discovery |
| 8 | + - Davinci Authorization |
| 9 | + - Token acquisition |
| 10 | + - UserInfo (protected, requires Bearer token) |
7 | 11 |
|
8 | | -## Creating an endpoint in the Api specification |
| 12 | +- Uses Effect and @effect/platform for functional, type-safe API definition and handling. |
| 13 | +- Includes middleware for logging, CORS, cookie management, and Bearer token authorization. |
9 | 14 |
|
10 | | -To create an endpoint, visit the [spec]('./src/spec.ts') file and add your endpoint. For organization and cleanliness, endpoints can be abstracted into the [endpoints]('./src/endpoints/') folder. |
| 15 | +## Tech Stack |
11 | 16 |
|
12 | | -When an endpoint is made, you can add it to the [`spec`]('./src/spec.ts') `pipe`. |
| 17 | +- [Effect](https://effect.website/) |
| 18 | +- [@effect/platform](https://github.com/Effect-TS/platform) |
| 19 | +- Node.js (via @effect/platform-node) |
| 20 | +- TypeScript |
13 | 21 |
|
14 | | -## Handling your new endpoint |
| 22 | +## Getting Started |
15 | 23 |
|
16 | | -When you have created an endpoint in the specification, you need to now handle the endpoint. This is the actual code implementation of your endpoint. |
| 24 | +### Install dependencies |
17 | 25 |
|
18 | | -handlers are saved in the [handlers]('./src/handlers/') folder. |
19 | | - |
20 | | -You use RouterBuilder.handler, passing in the [api spec]('./src/spec.ts') and the name of the route, and then an Effect returning function (`Effect.gen` is the simplest form of this). |
21 | | - |
22 | | -The request arguments, you define in your endpoint specification, (query params, path params, request bodies, etc) are the arguments passed to the callback function of `RouterBuilder.handler`. |
23 | | - |
24 | | -Ensure that you also add your `handler` to the RouterBuilder.handle [here]('./src/main.ts'); |
25 | | - |
26 | | -## Adding a journey / flow to the response map |
27 | | - |
28 | | -If you are adding a flow to the response map the first thing to do is open up [responseMap]('./src/responses/index.ts') |
29 | | - |
30 | | -This file is a `map` of Names -> Array<Step> where a Step is the response you want to return (in order). Order is key here. |
31 | | - |
32 | | -If the Response you want to return is not already defined as a schema, you will have to define a new Schema and add the response. |
33 | | - |
34 | | -A schema is defined in the schemas folder [here]('./src/schemas/'); |
35 | | -A response is defined in the responses folder [here]('./src/responses/') |
36 | | - |
37 | | -This is still a work in progress in terms of making it more scalable. |
38 | | - |
39 | | -## Validating your code in Test |
40 | | - |
41 | | -After adding a journey/flow to the response map and defining a schema, you next want to have some validation on the submitted request. You can do this by adding it to the `validator` function [here]('./src/helpers/match.ts'); |
42 | | - |
43 | | -This functions job is to `match` the type passed in, and validate based on the condition provided. If it passes, a boolean is returned, if it fails, a new Error should be returned. |
44 | | - |
45 | | -## Services |
46 | | - |
47 | | -To make it so types line up easier, each route, has a service dedicated to itself. The service under the hood, uses the `Requester` service. The `Requester` service is to mimic a call to the authorization server. |
48 | | - |
49 | | -Let's look at the `Authorize` service. This service is the workhorse of the `authorize` handler. |
| 26 | +```sh |
| 27 | +pnpm install |
| 28 | +``` |
50 | 29 |
|
51 | | -`Authorize`, the service, uses `Requester` which will fetch a response from the authorization server. |
| 30 | +### Build |
52 | 31 |
|
53 | | -After retrieving the response, the service will catch any errors that may be thrown, and mold them into HttpErrors to respond back to the client. |
| 32 | +```sh |
| 33 | +pnpm build |
| 34 | +``` |
54 | 35 |
|
55 | | -In a mock environment, rather than fetching from the client, authorization service will grab the next response from the `responseMap`. |
| 36 | +### Run the server |
56 | 37 |
|
57 | | -In a live environment, it will forward a request to the Fetch service, and return that response. |
| 38 | +```sh |
| 39 | +pnpm serve |
| 40 | +``` |
58 | 41 |
|
59 | | -## Creating Errors |
| 42 | +The server will start on port `9443`. |
60 | 43 |
|
61 | | -If you want to create an error, it is simple. This is the skeleton of how to create an Error in `Effect` |
| 44 | +### Run tests |
62 | 45 |
|
63 | | -``` |
64 | | -class MyErrorName { |
65 | | - readonly _tag = 'MyErrorName' |
66 | | -} |
| 46 | +```sh |
| 47 | +pnpm test |
67 | 48 | ``` |
68 | 49 |
|
69 | | -The `_tag` is important as this is the name of the error, and how we can `catchTags` in our error handling. For simplicity, you can name is the same as your error class. |
| 50 | +## API Overview |
70 | 51 |
|
71 | | -## Handling Errors |
| 52 | +### Healthcheck |
72 | 53 |
|
73 | | -We want to return our errors back to the client, but typically we need an error response body that informs the client of the issue. |
| 54 | +- `GET /healthcheck` |
| 55 | +- Returns `"Healthy"` if the server is running. |
74 | 56 |
|
75 | | -You should add your error responses to the response folder [here]('./src/responses'); |
| 57 | +### OpenID Connect Discovery |
76 | 58 |
|
77 | | -In the service where you want to handle your error, you will see a `catchTags` function. |
| 59 | +- `GET /:envid/as/.well-known/openid-configuration` |
| 60 | +- Returns a static OpenID configuration response. |
78 | 61 |
|
79 | | -Let's pause here to understand the `Effect` type. |
| 62 | +### Davinci Authorization |
80 | 63 |
|
81 | | -```ts |
82 | | -Effect<Success, Error, Requirements>; |
83 | | -``` |
| 64 | +- `GET /:envid/as/authorize` |
| 65 | +- Accepts query parameters for authorization. |
| 66 | +- Returns a mock authorization response. |
84 | 67 |
|
85 | | -When reading an Effect type, the first generic, is what is returned if the effect is successful. |
| 68 | +### Token Endpoint |
86 | 69 |
|
87 | | -The second argument is what is returned if the effect is unsuccessful. |
| 70 | +- `POST /:envid/as/token` |
| 71 | +- Returns a mock access token response. |
88 | 72 |
|
89 | | -The third argument is any services (or layers) that are required to run this effect. |
| 73 | +### UserInfo (Protected) |
90 | 74 |
|
91 | | -So if we have an `effect` like this `Effect<Users, HttpError.HttpError | NoSuchElementException, never>` |
| 75 | +- `GET /:envid/as/userinfo` |
| 76 | +- Requires a valid Bearer token. |
| 77 | +- Returns mock user information. |
92 | 78 |
|
93 | | -This tells us the `effect` returns `users`, and can error two ways, `NoSuchElementException`, and with an `HttpError`. |
| 79 | +### End Session |
94 | 80 |
|
95 | | -We would rather handle this `NoSuchElementException` and send back to the client an HttpError informing them of the error that occurred. |
| 81 | +- `GET /:envid/as/endSession` |
| 82 | +- Ends the user's session. |
| 83 | +- Accepts `post_logout_redirect_uri` and `state` query parameters for redirects. |
| 84 | +- Returns a confirmation message. |
96 | 85 |
|
97 | | -We can do something like this now |
98 | | - |
99 | | -```ts |
100 | | -Effect.catchTag('NoSuchElementException', () => |
101 | | - HttpError.unauthorizedError('no such element found'), |
102 | | -); |
103 | | -``` |
| 86 | +## Notes |
104 | 87 |
|
105 | | -This will return a 401 with that message. |
| 88 | +- The `customHtmlRoutes` and related endpoints are not currently implemented. |
| 89 | +- All endpoints return static or mock data for testing purposes. |
106 | 90 |
|
107 | | -When handling errors, we try to keep the handler always returning an `HttpError`, so we should handle any other errors we have deeper in the call stack, to return HttpError unless there is a valid reason to allow the error to bubble up. |
| 91 | +## License |
108 | 92 |
|
109 | | -If you have a shape of an error that you want to return from a handler, that does not match the current schema, you can add it to the `api spec`. |
| 93 | +MIT © Ping Identity Corporation |
0 commit comments