From 0ab5a8c023e9485e65c83d4c0c5f67b506af2620 Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Tue, 16 Nov 2021 16:33:40 +0000 Subject: [PATCH 1/5] feat: set up docs container and move spec documentation files The moved files are from the 'eligibility-server' project and were originally moved from the 'benefits' project. docker-compose.yml needed a small change with the build context in order for the 'COPY docs/requirements.txt ...' to work properly. --- .devcontainer/Dockerfile | 4 + .devcontainer/devcontainer.json | 2 +- .devcontainer/docker-compose.yml | 20 +- .markdownlint.yaml | 15 ++ docs/.pages | 3 + docs/README.md | 227 +++++++++++++++++++ docs/example-transactions.md | 372 +++++++++++++++++++++++++++++++ docs/requirements.txt | 6 + docs/token-signing-encryption.md | 37 +++ mkdocs.yml | 60 +++++ 10 files changed, 735 insertions(+), 11 deletions(-) create mode 100644 .markdownlint.yaml create mode 100644 docs/.pages create mode 100644 docs/README.md create mode 100644 docs/example-transactions.md create mode 100644 docs/requirements.txt create mode 100644 docs/token-signing-encryption.md create mode 100644 mkdocs.yml diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 99d2c78..84046c9 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -19,3 +19,7 @@ RUN python -m pip install --upgrade pip && \ # enter src directory WORKDIR /home/$USER/src + +# install docs tooling: +COPY docs/requirements.txt docs/requirements.txt +RUN pip install --no-cache-dir -r docs/requirements.txt diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index e0ebebe..e464c51 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -2,7 +2,7 @@ "name": "cal-itp/eligibility-api", "dockerComposeFile": ["./docker-compose.yml"], "service": "dev", - "runServices": ["dev"], + "runServices": ["dev", "docs"], "workspaceFolder": "/home/calitp/src", "postStartCommand": [ "/bin/bash", diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index aa3b9f4..3b7ee15 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -3,18 +3,18 @@ version: "3.8" services: dev: build: - context: . - dockerfile: Dockerfile + context: .. + dockerfile: /.devcontainer/Dockerfile entrypoint: [] command: sleep infinity image: eligibility_api:dev volumes: - ../:/home/calitp/src - # docs: - # image: eligibility_api:dev - # entrypoint: mkdocs - # command: serve --dev-addr "0.0.0.0:8000" - # ports: - # - "8000" - # volumes: - # - ../:/home/calitp/src:cached + docs: + image: eligibility_api:dev + entrypoint: mkdocs + command: serve --dev-addr "0.0.0.0:8000" + ports: + - "8000" + volumes: + - ../:/home/calitp/src diff --git a/.markdownlint.yaml b/.markdownlint.yaml new file mode 100644 index 0000000..051b0b2 --- /dev/null +++ b/.markdownlint.yaml @@ -0,0 +1,15 @@ +# includes/excludes all rules by default +default: true + +# 4-space list indentation works best with MkDocs +MD007: + indent: 4 + +# Remove line length limit - no one wants to hard wrap all their paragraphs +MD013: false + +# Allow inline HTML +MD033: false + +# Allow duplicate headers +MD024: false diff --git a/docs/.pages b/docs/.pages new file mode 100644 index 0000000..a6d0b63 --- /dev/null +++ b/docs/.pages @@ -0,0 +1,3 @@ +nav: + - API Specification: README.md + - ... diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..6db0f84 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,227 @@ +# API Specification + +This document outlines the requirements for data exchange to verify one or more eligibility criteria for transit benefits. + +## Security + +### Actors + +* The **API Client** communicates with an API Server on behalf of a user. The Client makes [Requests](#request). +* An **API Server** holds information enabling fulfillment of an eligibility verification transaction. The Server sends + [Responses](#response) + +Under these definitions, the [`benefits`](https://docs.calitp.org/benefits) application acts as the Client and communicates with this application, the Eligibility Server. + +### Authentication/Authorization + +The Server must be secured and allow only authorized Clients to make requests. API requests are secured via +client secrets sent with the HTTP headers. + +### Transport + +All API requests and responses must be made over an encrypted HTTPS connection utilizing [TLS][tls] 1.2 or higher. + +This API uses a data interchange format known as [JSON Web Token][jwt] (JWT); JWT is an [open industry standard][jwt-standard] +method of representing claims securely between two parties. Built into JWT are important protections for data integrity and +data source verification. + +### Message signing + +JWT is designed to use a digital signature, allowing the Server to verify that data received was not modified by a third-party +after being sent by the Client. Request and response payloads must be signed with public-key cryptography, which allows the +recipient to validate that the payload came from a known sender. + +Signing algorithms supporting public-key cryptography include the RSA family (e.g. RSA-256) and ECDSA. + +### Message encryption + +The Request JWT must be encrypted by the Client before sending, using a public key published by the Server. The Server's +Response JWT must also be encrypted, using a public key published by the Client. + +### Composing a message + +Based on [*connect2id Nested signed and encrypted JSON Web Token (JWT)*][connect2id]. + +1. Build JWT header and (request/response) payload (See [API documentation](#api-documentation)) +1. Generate signature for JWT using the sender’s private key +1. Put header, claims, and signature together into a Signed JWT (JWS) +1. Encrypt JWS using the recipient’s public key (JWE) +1. [Base64url-encode][b64e] JWE +1. JWE is sent with (request/response) + +## API Documentation + +Below is a description of the HTTP request and response, and request and response payloads. The payloads each make use of +[JWT Registered claims][jwt-registered] as well as [Private claims][jwt-private]. + +**All fields are required.** Fields names marked with ***** are Registered claims defined by the JWT specification. + +Complete example transactions can be found on [another page](example-transactions.md). + +### Header + +The same header is shared between Request and Response JWTs. + +| Field name | Data type | Notes | +|------------|-------------|-------------------------------------| +| `alg`***** | `string` | The signature algorithm | +| `enc`***** | `string` | The encryption algorithm | +| `typ`***** | `string` | The type of token; must equal "JWT" | + +**Header example:** + +```json +{ + "alg": "RS256", + "enc": "RS256", + "typ": "JWT" +} +``` + +### Request + +Requests are sent as HTTP GET requests to the Server: + +```http +GET /api/eligibility HTTP/1.1 +Host: verify.gov +Authorization: Bearer + +``` + +The URL endpoint is defined by the implementing Server. The Request JWT is sent as a Base64url-encoded Bearer token in the +`Authorization` header. There is no Request Body and querystring parameters are undefined. + +**Request JWT payload:** + +| Field name | Data type | Notes | +|---------------|-----------------|--------------------------------------------------------------------------------------------| +| `jti`***** | [`UUID4`][uuid] | Unique identifier for this JWT | +| `iss`***** | `string` | Identifier for the issuer of the JWT (e.g. the Client) | +| `iat`***** | `integer` | The time at which the JWT was issued; expressed as [Unix][unix] seconds | +| `agency` | `string` | Identifier for the transit agency the JWT was issued on behalf of | +| `eligibility` | `string[]` | An array of [eligibility types][types] to verify | +| `sub`***** | `string` | The subject of the JWT, expressed as the transit rider's ID (e.g. Driver's License number) | +| `name` | `string` | The transit rider's last name | + +**Request payload example:** + +```json +{ + "jti": "0890cce7-25d3-425c-a81b-bc437c2e18a3", + "iss": "https://calitp.org", + "iat": 1632893416, + "agency": "ABC Transit Company", + "eligibility": [ + "senior" + ], + "sub": "A1234567", + "name": "Garcia" +} +``` + +### Response + +The response body contains the Base64url-encoded Response JWT: + +```http +HTTP/1.1 200 OK +Date: Wed, 29 Sep 2021 05:30:17 GMT +Content-Type: text/plain; charset=UTF-8 +Content-Length: 232 + +JWT +``` + +**Response JWT payload:** + +The Server response is intentionally sparse, omitting all PII from the original Request. + +| Field name | Data type | Notes | +|---------------|-----------------|--------------------------------------------------------------------------------------| +| `jti`***** | [`UUID4`][uuid] | The identifier from the Request JWT | +| `iss`***** | `string` | Identifier for the issuer of the JWT (e.g. the Server) | +| `iat`***** | `integer` | The time at which the JWT was issued; expressed as [Unix][unix] seconds | +| `eligibility` | `string[]` | An array of [eligibility types][types] that verify as `TRUE` for the Request | + +**Response payload example:** + +```json +{ + "jti": "0890cce7-25d3-425c-a81b-bc437c2e18a3", + "iss": "https://verify.gov", + "iat": 1632893417, + "eligibility": [ + "senior" + ] +} +``` + +### Errors + +An error can occur if the Request does not contain appropriate data. Errors are returned as JWT payloads in the same way that +regular Responses are returned, with a HTTP code 400. + +```http +HTTP/1.1 400 Bad Request +Date: Wed, 29 Sep 2021 05:30:17 GMT +Content-Type: text/plain; charset=UTF-8 +Content-Length: 232 + +JWT +``` + +**Error JWT payload:** + +| Field name | Data type | Notes | +|---------------|--------------------|-------------------------------------------------------------------------| +| `jti`***** | [`UUID4`][uuid] | The identifier from the Request JWT | +| `iss`***** | `string` | Identifier for the issuer of the JWT (e.g. the Server) | +| `iat`***** | `integer` | The time at which the JWT was issued; expressed as [Unix][unix] seconds | +| `error` | `{string: string}` | A dictionary mapping field name to error message | + +#### Example: missing value + +Occurs when one or more fields are missing (either missing from the payload, or with a null/empty value). + +```json +{ + "jti": "0890cce7-25d3-425c-a81b-bc437c2e18a3", + "iss": "https://verify.gov", + "iat": 1632893417, + "error": { + "eligibility": "missing" + } +} +``` + +#### Example: invalid format + +Occurs when one or more fields contain data that is invalid according to the Server's interpretation. + +```json +{ + "jti": "0890cce7-25d3-425c-a81b-bc437c2e18a3", + "iss": "https://verify.gov", + "iat": 1632893417, + "error": { + "sub": "invalid" + } +} +``` + +### Eligibility types + +Naturally, the Client and Server must agree on values for the `eligibility` array. Typically the Server's definition(s) and +type(s) will be agreed upon and used by the Client, as the server is responsible for determining eligibility of a given type. + +[b64e]: https://en.wikipedia.org/wiki/Base64#The_URL_applications +[connect2id]: https://connect2id.com/products/nimbus-jose-jwt/examples/signed-and-encrypted-jwt +[jwt]: https://jwt.io/introduction/ +[jwt-private]: https://tools.ietf.org/html/rfc7519#section-4.3 +[jwt-registered]: https://tools.ietf.org/html/rfc7519#section-4.1 +[jwt-standard]: https://tools.ietf.org/html/rfc7519 +[tls]: https://en.wikipedia.org/wiki/Transport_Layer_Security +[types]: #eligibility-types +[unix]: https://en.wikipedia.org/wiki/Unix_time +[uuid]: https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_4_(random) diff --git a/docs/example-transactions.md b/docs/example-transactions.md new file mode 100644 index 0000000..a8ece38 --- /dev/null +++ b/docs/example-transactions.md @@ -0,0 +1,372 @@ +# Example transactions + +This page outlines example roundtrip HTTP transactions conforming to the [Eligibility Verification API](README.md). + +## Sample server + +For the following examples, assume a Server with a database like: + +| Driver's License Number | Last Name | Date of Birth | +|-------------------------|-----------|---------------| +| A1234567 | Garcia | 1955-08-27 | +| B2345678 | Hernandez | 1961-01-23 | + +Further, assume the Server validates the eligibility type `senior` for those individuals age 65 or older. + +## Usage of JWT in examples + +For the purposes of these examples, JWT signing will be done using the simpler, secret-based [HMAC SHA-256][hs256] (HS256) +signing algorithm. **This is not appropriate for production** as it does not carry the same guarantees as a public-key signing +algorithm. + +## Example JWT header + +Although the header will indicate otherwise, for simplification the examples *will not show encryption/decryption* of the JWT. + +The JWT header (both Request and Response) for each of the following examples is: + +```json +{ + "alg": "HS256", + "enc": "RS256", + "typ": "JWT" +} +``` + +## Test encoding/decoding + +To test JWT encoding/decoding, use the Debugger tool on [JWT.IO][jwtio]. Paste in an encoded key to get the decoded output. +Or build decoded output to see the corresponding encoded key. + +**This tool must not be used with real (PII) data.** + +## Examples + +### 1. Eligible senior + +> This JWT was issued at 2021/09/29 05:30:16 (UTC); the subject (Garcia) is 66 years old. + +#### Request JWT payload + +```json +{ + "jti": "0890cce7-25d3-425c-a81b-bc437c2e18a3", + "iss": "https://calitp.org", + "iat": 1632893416, + "agency": "ABC Transit Company", + "eligibility": [ + "senior" + ], + "sub": "A1234567", + "name": "Garcia" +} +``` + +#### HTTP Request + +The preceding header and payload result in the (signed, Base64url-encoded) JWT used in the following `Authorization` header: + +```http +GET /api/eligibility HTTP/1.1 +Host: verify.gov +Authorization: Bearer eyJhbGciOiJIUzI1NiIsImVuYyI6IlJTMjU2IiwidHlwIjoiSldUIn0.ey +JqdGkiOiIwODkwY2NlNy0yNWQzLTQyNWMtYTgxYi1iYzQzN2MyZTE4YTMiLCJpc3MiOiJodHRwczovL2 +NhbGl0cC5vcmciLCJpYXQiOjE2MzI4OTM0MTYsImFnZW5jeSI6IkFCQyBUcmFuc2l0IENvbXBhbnkiLC +JlbGlnaWJpbGl0eSI6WyJzZW5pb3IiXSwic3ViIjoiQTEyMzQ1NjciLCJuYW1lIjoiR2FyY2lhIn0.sM +VsPU4ByJNR9lADrjlZHeNi1NkBoPdXO50fnCFDDqM +``` + +#### HTTP Response + +```http +HTTP/1.1 200 OK +Date: Wed, 29 Sep 2021 05:30:17 GMT +Content-Type: text/plain; charset=UTF-8 +Content-Length: 254 + +eyJhbGciOiJIUzI1NiIsImVuYyI6IlJTMjU2IiwidHlwIjoiSldUIn0.eyJqdGkiOiIwODkwY2NlNy0y +NWQzLTQyNWMtYTgxYi1iYzQzN2MyZTE4YTMiLCJpc3MiOiJodHRwczovL3ZlcmlmeS5nb3YiLCJpYXQi +OjE2MzI4OTM0MTcsImVsaWdpYmlsaXR5IjpbInNlbmlvciJdfQ.tos2vJOO6msv9tMDMT34f95aIRvYj +sHRVUz5621fNlI +``` + +#### Response JWT payload + +Base64url-decoding the JWT in the response body yields the following payload: + +```json +{ + "jti": "0890cce7-25d3-425c-a81b-bc437c2e18a3", + "iss": "https://verify.gov", + "iat": 1632893417, + "eligibility": [ + "senior" + ] +} +``` + +The presence of the value `"senior"` in the `eligibility` array indicates that the Request subject associated with this JWT +(Garcia) has been verified for that eligibility. + +### 2. Ineligible senior + +> This JWT was issued at 2021/09/29 05:30:16 (UTC), meaning the subject (Hernandez) is 60 years old. + +#### Request JWT payload + +```json +{ + "jti": "b2bb29dc-6f6a-44a2-83cf-e298123bbbd2", + "iss": "https://calitp.org", + "iat": 1632893416, + "agency": "ABC Transit Company", + "eligibility": [ + "senior" + ], + "sub": "B2345678", + "name": "Hernandez" +} +``` + +#### HTTP Request + +The preceding header and payload result in the (signed, Base64url-encoded) JWT used in the following `Authorization` header: + +```http +GET /api/eligibility HTTP/1.1 +Host: verify.gov +Authorization: Bearer eyJhbGciOiJIUzI1NiIsImVuYyI6IlJTMjU2IiwidHlwIjoiSldUIn0.ey +JqdGkiOiJiMmJiMjlkYy02ZjZhLTQ0YTItODNjZi1lMjk4MTIzYmJiZDIiLCJpc3MiOiJodHRwczovL2 +NhbGl0cC5vcmciLCJpYXQiOjE2MzI4OTM0MTYsImFnZW5jeSI6IkFCQyBUcmFuc2l0IENvbXBhbnkiLC +JlbGlnaWJpbGl0eSI6WyJzZW5pb3IiXSwic3ViIjoiQjIzNDU2NzgiLCJuYW1lIjoiSGVybmFuZGV6In +0.iY58E7ZYQziQ8ZH7iGSwPGp9S1xbFm6JLXFK0D2E-0w +``` + +#### HTTP Response + +```http +HTTP/1.1 200 OK +Date: Wed, 29 Sep 2021 05:30:17 GMT +Content-Type: text/plain; charset=UTF-8 +Content-Length: 243 + +eyJhbGciOiJIUzI1NiIsImVuYyI6IlJTMjU2IiwidHlwIjoiSldUIn0.eyJqdGkiOiJiMmJiMjlkYy02 +ZjZhLTQ0YTItODNjZi1lMjk4MTIzYmJiZDIiLCJpc3MiOiJodHRwczovL3ZlcmlmeS5nb3YiLCJpYXQi +OjE2MzI4OTM0MTcsImVsaWdpYmlsaXR5IjpbXX0._hE8UJPYSmQ0q6xymx8UIVF8BrlZry-G82g9ssyP +dO4 +``` + +#### Response JWT payload + +Base64url-decoding the JWT in the response body yields the following payload: + +```json +{ + "jti": "b2bb29dc-6f6a-44a2-83cf-e298123bbbd2", + "iss": "https://verify.gov", + "iat": 1632893417, + "eligibility": [] +} +``` + +The absence of a value in the `eligibility` array indicates that the Request subject associated with this JWT (Hernandez) has +not been verified for any eligibility. + +### 3. No eligibility data + +> No data on the subject (Smith) exists in the Server's database. + +#### *Request JWT payload + +```json +{ + "jti": "ef8e9805-bb1b-4f97-903b-6b9ab830d604", + "iss": "https://calitp.org", + "iat": 1632893416, + "agency": "ABC Transit Company", + "eligibility": [ + "senior" + ], + "sub": "C3456789", + "name": "Smith" +} +``` + +#### HTTP Request + +The preceding header and payload result in the (signed, Base64url-encoded) JWT used in the following `Authorization` header: + +```http +GET /api/eligibility HTTP/1.1 +Host: verify.gov +Authorization: Bearer eyJhbGciOiJIUzI1NiIsImVuYyI6IlJTMjU2IiwidHlwIjoiSldUIn0.ey +JqdGkiOiJlZjhlOTgwNS1iYjFiLTRmOTctOTAzYi02YjlhYjgzMGQ2MDQiLCJpc3MiOiJodHRwczovL2 +NhbGl0cC5vcmciLCJpYXQiOjE2MzI4OTM0MTYsImFnZW5jeSI6IkFCQyBUcmFuc2l0IENvbXBhbnkiLC +JlbGlnaWJpbGl0eSI6WyJzZW5pb3IiXSwic3ViIjoiQzM0NTY3ODkiLCJuYW1lIjoiU21pdGgifQ.0xp +eyL3GRAQGrGfvreruTra7dbJpjQQ0zLiIqm4H7sE +``` + +#### HTTP Response + +```http +HTTP/1.1 200 OK +Date: Wed, 29 Sep 2021 05:30:17 GMT +Content-Type: text/plain; charset=UTF-8 +Content-Length: 246 + +eyJhbGciOiJIUzI1NiIsImVuYyI6IlJTMjU2IiwidHlwIjoiSldUIn0.eyJqdGkiOiJlZjhlOTgwNS1i +YjFiLTRmOTctOTAzYi02YjlhYjgzMGQ2MDQiLCJpc3MiOiJodHRwczovL3ZlcmlmeS5nb3YiLCJpYXQi +OjE2MzI4OTM0MTcsImVsaWdpYmlsaXR5IjpbXX0.LEITzkSGL4Y7uA30pRYxNG7XjDI0lSYtev5X7hNK +Gn4 +``` + +#### Response JWT payload + +Base64url-decoding the JWT in the response body yields the following payload: + +```json +{ + "jti": "ef8e9805-bb1b-4f97-903b-6b9ab830d604", + "iss": "https://verify.gov", + "iat": 1632893417, + "eligibility": [] +} +``` + +The absence of a value in the `eligibility` array indicates that the Request subject associated with this JWT (Smith) has not +been verified for any eligibility. + +**Note** it is important to return an empty `eligibility` array rather than an error message or 4xx HTTP code here. +This way there is no distinction between "exists in the database" and "does not exist in the database". + +### 4. Missing request data + +> The request lacks a `sub` property, which is required. + +#### Request JWT payload + +```json +{ + "jti": "b692fa7c-3dca-4d0d-90ba-e5415af48285", + "iss": "https://calitp.org", + "iat": 1632893416, + "agency": "ABC Transit Company", + "eligibility": [ + "senior" + ], + "name": "Garcia" +} +``` + +#### HTTP Request + +The preceding header and payload result in the (signed, Base64url-encoded) JWT used in the following `Authorization` header: + +```http +GET /api/eligibility HTTP/1.1 +Host: verify.gov +Authorization: Bearer eyJhbGciOiJIUzI1NiIsImVuYyI6IlJTMjU2IiwidHlwIjoiSldUIn0.ey +JqdGkiOiJiNjkyZmE3Yy0zZGNhLTRkMGQtOTBiYS1lNTQxNWFmNDgyODUiLCJpc3MiOiJodHRwczovL2 +NhbGl0cC5vcmciLCJpYXQiOjE2MzI4OTM0MTYsImFnZW5jeSI6IkFCQyBUcmFuc2l0IENvbXBhbnkiLC +JlbGlnaWJpbGl0eSI6WyJzZW5pb3IiXSwibmFtZSI6IkdhcmNpYSJ9.EtnDvEHY1CjldnH-98dIMwdir +pxbNbuCg18R7uR8Gag +``` + +#### HTTP Response + +```http +HTTP/1.1 400 Bad Request +Date: Wed, 29 Sep 2021 05:30:17 GMT +Content-Type: text/plain; charset=UTF-8 +Content-Length: 258 + +eyJhbGciOiJIUzI1NiIsImVuYyI6IlJTMjU2IiwidHlwIjoiSldUIn0.eyJqdGkiOiJiNjkyZmE3Yy0z +ZGNhLTRkMGQtOTBiYS1lNTQxNWFmNDgyODUiLCJpc3MiOiJodHRwczovL3ZlcmlmeS5nb3YiLCJpYXQi +OjE2MzI4OTM0MTcsImVycm9yIjp7InN1YiI6Im1pc3NpbmcifX0.1Z53Z2PInyTSQRomcWhcC2Z3c_qL +WoISH7eFv-_JJnE +``` + +#### Response JWT payload + +Base64url-decoding the JWT in the response body yields the following payload: + +```json +{ + "jti": "b692fa7c-3dca-4d0d-90ba-e5415af48285", + "iss": "https://verify.gov", + "iat": 1632893417, + "error": { + "sub": "missing" + } +} +``` + +The `error` message indicates that the Request subject associated with this JWT is missing. + +### 5. Invalid request data + +> The request's `sub` property is not in the correct format. + +#### Request JWT payload + +```json +{ + "jti": "d0dbacaf-e691-4ecc-a733-a42a904da607", + "iss": "https://calitp.org", + "iat": 1632893416, + "agency": "ABC Transit Company", + "eligibility": [ + "senior" + ], + "sub": "12345678Z", + "name": "Garcia" +} +``` + +#### HTTP Request + +The preceding header and payload result in the (signed, Base64url-encoded) JWT used in the following `Authorization` header: + +```http +GET /api/eligibility HTTP/1.1 +Host: verify.gov +Authorization: Bearer eyJhbGciOiJIUzI1NiIsImVuYyI6IlJTMjU2IiwidHlwIjoiSldUIn0.ey +JqdGkiOiJkMGRiYWNhZi1lNjkxLTRlY2MtYTczMy1hNDJhOTA0ZGE2MDciLCJpc3MiOiJodHRwczovL2 +NhbGl0cC5vcmciLCJpYXQiOjE2MzI4OTM0MTYsImFnZW5jeSI6IkFCQyBUcmFuc2l0IENvbXBhbnkiLC +JlbGlnaWJpbGl0eSI6WyJzZW5pb3IiXSwic3ViIjoiMTIzNDU2NzhaIiwibmFtZSI6IkdhcmNpYSJ9.2 +w5JhbfIzOSdKWTOrP5CQdhWw9Vo8VunoASe4EVZOoI +``` + +#### HTTP Response + +```http +HTTP/1.1 400 Bad Request +Date: Wed, 29 Sep 2021 05:30:17 GMT +Content-Type: text/plain; charset=UTF-8 +Content-Length: 258 + +eyJhbGciOiJIUzI1NiIsImVuYyI6IlJTMjU2IiwidHlwIjoiSldUIn0.eyJqdGkiOiJkMGRiYWNhZi1l +NjkxLTRlY2MtYTczMy1hNDJhOTA0ZGE2MDciLCJpc3MiOiJodHRwczovL3ZlcmlmeS5nb3YiLCJpYXQi +OjE2MzI4OTM0MTcsImVycm9yIjp7InN1YiI6ImludmFsaWQifX0.V_8VA7vWTzwibGE4mfyQ0zAwKhLV +qKDYsl2M55z8rDc +``` + +#### Response JWT payload + +Base64url-decoding the JWT in the response body yields the following payload: + +```json +{ + "jti": "d0dbacaf-e691-4ecc-a733-a42a904da607", + "iss": "https://verify.gov", + "iat": 1632893417, + "error": { + "sub": "invalid" + } +} +``` + +The `error` message indicates that the Request subject associated with this JWT was invalid. + +[hs256]: https://en.wikipedia.org/wiki/SHA-2 +[jwtio]: https://jwt.io/ diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000..80d1f6e --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,6 @@ +fontawesome_markdown +mkdocs +mkdocs-awesome-pages-plugin +mkdocs-macros-plugin +mkdocs-material +mkdocstrings diff --git a/docs/token-signing-encryption.md b/docs/token-signing-encryption.md new file mode 100644 index 0000000..19682dd --- /dev/null +++ b/docs/token-signing-encryption.md @@ -0,0 +1,37 @@ +# Token signing and encryption + +The Eligiblity Verification API makes use of Signed and Encrypted JSON Web Tokens (JWS, JWE, JWT) as a means of data transfer. + +A public/private keypair must be generated by each party (Benefits Client and Eligibility Verification Server). +[Example keys](https://github.com/cal-itp/benefits/tree/dev/localhost/keys) are included for the test verification server +and sample agencies. + +## Generating new keypairs + +Using a terminal like `bash`, and the `openssl` program: + +```bash +openssl genrsa -out [file name].key 2048 +``` + +## Extract the public key + +```bash +openssl rsa -in [private key created above].key -pubout > [file name].pub +``` + +There are two new files: + +* `[file name].key`: private key in PEM format, apply to a `TransitAgency` instance in Django +* `[file name].pub`: public key in PEM format, give to the Eligibility Verification server + +A public key in PEM format from the Eligibility Verification server is also required, and must be applied to an +`EligiblityVerifier` instance in Django. + +## Format for config file + +To get a single-line version of a PEM key, suitable for a JSON configuration file: + +```bash +awk 'NF {sub(/\r/, ""); printf "%s\\n",$0;}' +``` diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..1762529 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,60 @@ +site_name: "cal-itp/eligibility-api: documentation" +site_url: https://docs.calitp.org/eligibility-api +repo_url: https://github.com/cal-itp/eligibility-api +edit_uri: edit/main/docs + +theme: + name: material + features: + - navigation.tabs + palette: + - media: "(prefers-color-scheme: light)" + scheme: default + primary: blue + accent: amber + toggle: + icon: material/toggle-switch-off-outline + name: Switch to dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + primary: blue + accent: amber + toggle: + icon: material/toggle-switch + name: Switch to light mode + +extra: + analytics: + provider: google + property: G-SZB618VNBZ + +plugins: + - search + - awesome-pages + +extra_javascript: + - https://unpkg.com/mermaid@8.5.0/dist/mermaid.min.js + +extra_css: + - https://use.fontawesome.com/releases/v5.13.0/css/all.css + +markdown_extensions: + - admonition + - codehilite: + linenums: true + - pymdownx.inlinehilite + - pymdownx.tasklist: + custom_checkbox: true + - pymdownx.tabbed + - pymdownx.superfences: + custom_fences: + - name: mermaid + class: mermaid + format: !!python/name:pymdownx.superfences.fence_div_format + - pymdownx.smartsymbols + - meta + - toc: + # insert a blank space before the character + permalink: " ¶" + - smarty + - fontawesome_markdown From 31a768db8fba62d77e04f1385500459a49621370 Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Tue, 16 Nov 2021 16:55:00 +0000 Subject: [PATCH 2/5] feat: make small wording edit to README.md --- docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index 6db0f84..d2f224a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -10,7 +10,7 @@ This document outlines the requirements for data exchange to verify one or more * An **API Server** holds information enabling fulfillment of an eligibility verification transaction. The Server sends [Responses](#response) -Under these definitions, the [`benefits`](https://docs.calitp.org/benefits) application acts as the Client and communicates with this application, the Eligibility Server. +Under these definitions, the [`benefits`](https://docs.calitp.org/benefits) application acts as the Client and communicates with the Server, the [`eligibility-server`](https://docs.calitp.org/eligibility-server) application. ### Authentication/Authorization From 77b50b61247b7b8eb24aa92ca81b228d3d9f13b0 Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Tue, 16 Nov 2021 17:04:43 +0000 Subject: [PATCH 3/5] feat: add markdownlint-cli to pre-commit config and fix errors it found --- .github/dependabot.yml | 1 - .pre-commit-config.yaml | 5 +++++ README.md | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index ff8fda3..663bc4a 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -25,4 +25,3 @@ updates: include: "scope" labels: - "dependencies" - diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 07001d1..d1b87ba 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,3 +15,8 @@ repos: - id: check-yaml args: ["--unsafe"] - id: check-added-large-files + + - repo: https://github.com/igorshubovych/markdownlint-cli + rev: v0.27.1 + hooks: + - id: markdownlint diff --git a/README.md b/README.md index 1203f52..817993b 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Eligibility Verification API + This package encapsulates the data exchange needed to verify one or more eligibility criteria for transit benefits. ## License + [Apache 2.0 License](./LICENSE) From 30dd8c485a9a72f14f28aad7f89a00178a54382f Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Tue, 16 Nov 2021 17:13:55 +0000 Subject: [PATCH 4/5] feat: put back ':cached' on docs volume config --- .devcontainer/docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index 3b7ee15..c63e80d 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -17,4 +17,4 @@ services: ports: - "8000" volumes: - - ../:/home/calitp/src + - ../:/home/calitp/src:cached From 1ac422363f4393922d10951195e8a95a2cce29b8 Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Tue, 16 Nov 2021 18:15:10 +0000 Subject: [PATCH 5/5] fix: remove typo in Docker compose file and make edits to documentation --- .devcontainer/docker-compose.yml | 2 +- docs/{ => specification}/.pages | 0 docs/{ => specification}/README.md | 2 +- .../examples.md} | 0 docs/{ => specification}/token-signing-encryption.md | 11 ++++++----- 5 files changed, 8 insertions(+), 7 deletions(-) rename docs/{ => specification}/.pages (100%) rename docs/{ => specification}/README.md (99%) rename docs/{example-transactions.md => specification/examples.md} (100%) rename docs/{ => specification}/token-signing-encryption.md (57%) diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index c63e80d..1a41448 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -4,7 +4,7 @@ services: dev: build: context: .. - dockerfile: /.devcontainer/Dockerfile + dockerfile: .devcontainer/Dockerfile entrypoint: [] command: sleep infinity image: eligibility_api:dev diff --git a/docs/.pages b/docs/specification/.pages similarity index 100% rename from docs/.pages rename to docs/specification/.pages diff --git a/docs/README.md b/docs/specification/README.md similarity index 99% rename from docs/README.md rename to docs/specification/README.md index d2f224a..e5df105 100644 --- a/docs/README.md +++ b/docs/specification/README.md @@ -56,7 +56,7 @@ Below is a description of the HTTP request and response, and request and respons **All fields are required.** Fields names marked with ***** are Registered claims defined by the JWT specification. -Complete example transactions can be found on [another page](example-transactions.md). +Complete example transactions can be found on [another page](examples.md). ### Header diff --git a/docs/example-transactions.md b/docs/specification/examples.md similarity index 100% rename from docs/example-transactions.md rename to docs/specification/examples.md diff --git a/docs/token-signing-encryption.md b/docs/specification/token-signing-encryption.md similarity index 57% rename from docs/token-signing-encryption.md rename to docs/specification/token-signing-encryption.md index 19682dd..183641a 100644 --- a/docs/token-signing-encryption.md +++ b/docs/specification/token-signing-encryption.md @@ -2,8 +2,8 @@ The Eligiblity Verification API makes use of Signed and Encrypted JSON Web Tokens (JWS, JWE, JWT) as a means of data transfer. -A public/private keypair must be generated by each party (Benefits Client and Eligibility Verification Server). -[Example keys](https://github.com/cal-itp/benefits/tree/dev/localhost/keys) are included for the test verification server +A public/private keypair must be generated by each party (Client and Server). +Example keys for the [Client](https://github.com/cal-itp/benefits/tree/dev/localhost/keys) and [Server](https://github.com/cal-itp/eligibility-server/tree/main/keys) are included for the test verification server and sample agencies. ## Generating new keypairs @@ -22,11 +22,12 @@ openssl rsa -in [private key created above].key -pubout > [file name].pub There are two new files: -* `[file name].key`: private key in PEM format, apply to a `TransitAgency` instance in Django +* `[file name].key`: private key in PEM format, needed by the Client only * `[file name].pub`: public key in PEM format, give to the Eligibility Verification server -A public key in PEM format from the Eligibility Verification server is also required, and must be applied to an -`EligiblityVerifier` instance in Django. +The Client also requires a public key from the Eligibility Verification server, so this process must be repeated to generate the Server's keypair. + +The Server's private key should not be shared. ## Format for config file