Compare commits
215 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 96453e1d15 | |||
| fd4f9c4052 | |||
| ce5fd95bed | |||
|
972cf3ba45
|
|||
| 9d410abe54 | |||
| e740676de2 | |||
| acb0568725 | |||
|
6558d027ca
|
|||
| 17bad3a988 | |||
| d50387feb1 | |||
| 083125a5bb | |||
| 66b4fb90cd | |||
| 617812a044 | |||
| cc3a402fe4 | |||
| fc6882624f | |||
| 3825ee0656 | |||
| 237fa7e381 | |||
| b953439d0f | |||
| a74bd86fe2 | |||
| f52ccf28bc | |||
| de61dda74e | |||
| eb8bb78766 | |||
| f3ff720eb6 | |||
| b5fde43240 | |||
| 3bca5a318e | |||
| 22dbf00182 | |||
| d1af6008fd | |||
| bd54fd68f8 | |||
| 75035ae716 | |||
| 933dd886ff | |||
| ad85ce8347 | |||
| 361a2d23bc | |||
| 1b656ce2f9 | |||
| eba28529a6 | |||
| e8155bfd0a | |||
| 7b8dfb2cff | |||
| a5f091efa6 | |||
| df48b03d01 | |||
| 98e8a2abb6 | |||
| 854aef9b8d | |||
| 3f2e1b588f | |||
| f573a3a60c | |||
| ee0e44671c | |||
| 75e5abe166 | |||
| 3c5127d3c0 | |||
| 3aef5e33fa | |||
| e2b0c6422e | |||
| 7f9320dff2 | |||
| bfa14ce1bd | |||
| 950a706bb6 | |||
| 7e7828777d | |||
| 1f6c92b497 | |||
| b6c2971563 | |||
|
6d0327b01d
|
|||
| 46efd38035 | |||
| bdb9fbd405 | |||
| 68851ed10f | |||
| f20913a0ba | |||
| 338528f219 | |||
| 172174bd3e | |||
| e399262daa | |||
| d2b3c8ee17 | |||
|
d146ab76d8
|
|||
| a986a5f261 | |||
| f02427b128 | |||
| ecab345fc4 | |||
| d6c2b9dc78 | |||
| 217e131d0b | |||
| bf28e3955b | |||
| 8648217bcc | |||
| f8834133b7 | |||
| 205fce3829 | |||
|
b42f77f58d
|
|||
| e25bb11dc5 | |||
| 77c59422dc | |||
| 96ff4d45b3 | |||
| b5bf44c687 | |||
| e4df8438cf | |||
|
a9fe91bd84
|
|||
| b0c639b509 | |||
| 03429f2421 | |||
| 7b3e192cf0 | |||
| 8f098d60bd | |||
|
fa022bb9e0
|
|||
| 004cfc400a | |||
| d8f853f544 | |||
| 382f04534f | |||
| 57e5c08c79 | |||
| afbda1fe24 | |||
| 9f043b25e6 | |||
| c18259ac3f | |||
| 613abecf2a | |||
| 3e98eee171 | |||
| b4f482d293 | |||
| 3ae4d5abd2 | |||
| 83fd1153d9 | |||
| 060d9529bd | |||
|
8dc80e28cd
|
|||
| 94514bc627 | |||
| 34897e0015 | |||
| 86d4176015 | |||
| 68da5aa9a1 | |||
| f9743ca018 | |||
| b87a6002fd | |||
| 36dab87552 | |||
| f959811b68 | |||
| 39588214a6 | |||
|
8c7b44974f
|
|||
| afe64a31f9 | |||
|
45224547d6
|
|||
| d8aa7d2a89 | |||
| 72f3787dc6 | |||
| 2b62a7ed77 | |||
| 9500f5569d | |||
| 0a1f3eb4e4 | |||
| 7136bda3a8 | |||
| 790399edca | |||
| 6b1ea50aac | |||
| 5edd0801e4 | |||
| 5775cb6006 | |||
| 11ec56bae9 | |||
| 4ae4966ce9 | |||
|
9f40b06ce9
|
|||
| d300500da7 | |||
| 82f4604e6f | |||
| 04e402cdae | |||
| e6e1b1e348 | |||
| aaab5b2cd5 | |||
| 32af389b7b | |||
| f60af84b41 | |||
| 9cb454c32a | |||
| 8f31c1d717 | |||
| 277b64019c | |||
|
66f83fa70d
|
|||
| 01ccab5024 | |||
| 656b10bab9 | |||
| 366113bbf5 | |||
| dde5e91968 | |||
| 21ccdc6809 | |||
| b6b5673ea4 | |||
| b40c5b85e8 | |||
| 295636e444 | |||
| 5c7a189bf1 | |||
| 60ae679d87 | |||
| bc1dcdc8b4 | |||
| 81673c01eb | |||
| 0e883190d5 | |||
| bb29d2f2ad | |||
| 55374c395d | |||
| 8d94a8f8af | |||
| 3478ad0e5b | |||
|
b9a1c0b285
|
|||
| 3382e03305 | |||
| f97c03772b | |||
| 488d3a8b7f | |||
| a42c8c3251 | |||
| 411dd3d222 | |||
| acbea4996f | |||
| e0df0465aa | |||
| e7c4bfcdb7 | |||
| 041b66a54e | |||
| ce5467682e | |||
| 860b658fd8 | |||
| 5e0922d84f | |||
| ce35c1eb50 | |||
| 44047f88e4 | |||
| a9633e9079 | |||
| ef9654adfa | |||
| e99a340485 | |||
| d362203165 | |||
| afa1893f1a | |||
| 791a29d5b8 | |||
| d34a738b71 | |||
| 6e0bb8e1ca | |||
| c29f8be74c | |||
| 349989bb67 | |||
| d940a04d05 | |||
| 192929229f | |||
| 397d60361e | |||
| bb8deb221e | |||
| a465ae543a | |||
| 8aad0d6308 | |||
| 0166537d2f | |||
| 9f6e63ea50 | |||
| a30cb4b08c | |||
| abbc55ea6e | |||
| 779d78cdc6 | |||
| cf958ffa85 | |||
| eb579289f0 | |||
| d1ff4ec278 | |||
| 14c2c8f366 | |||
| 7177ba9516 | |||
| 652483e2de | |||
| 4e43361217 | |||
| 6dda660e78 | |||
| 534772b315 | |||
| 3bdfe7bf0e | |||
| edba76d0ab | |||
| 5289b4fa23 | |||
| eef7168f37 | |||
| 596967ff72 | |||
| 5f2385a92f | |||
| a5653c8ea6 | |||
| 75ec899c99 | |||
| cb31381be2 | |||
| 9ee344311a | |||
| d7e3b10e80 | |||
| 7b306dd500 | |||
| 22d096a2be | |||
| 858cb96e10 | |||
| e8dd55208c | |||
| dbf5206c1b | |||
| 4229508bba | |||
| b4d5dbe9e3 | |||
| b476cf0e36 |
@@ -1,2 +1,3 @@
|
||||
node_modules/
|
||||
.idea/
|
||||
.claude/
|
||||
|
||||
+4
-9
@@ -1,22 +1,17 @@
|
||||
include:
|
||||
- template: 'Workflows/MergeRequest-Pipelines.gitlab-ci.yml'
|
||||
- project: unboundsoftware/ci-templates
|
||||
file: Defaults.gitlab-ci.yml
|
||||
- project: unboundsoftware/ci-templates
|
||||
file: Release.gitlab-ci.yml
|
||||
|
||||
stages:
|
||||
- build
|
||||
|
||||
variables:
|
||||
DOCKER_HOST: tcp://docker:2376
|
||||
DOCKER_TLS_CERTDIR: "/certs"
|
||||
DOCKER_TLS_VERIFY: 1
|
||||
DOCKER_CERT_PATH: "$DOCKER_TLS_CERTDIR/client"
|
||||
DOCKER_DRIVER: overlay2
|
||||
|
||||
image: buildtool/build-tools:${BUILDTOOLS_VERSION}
|
||||
|
||||
build:
|
||||
stage: build
|
||||
services:
|
||||
- docker:dind
|
||||
script:
|
||||
- build
|
||||
- push
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
open-pull-requests-limit: 20
|
||||
- package-ecosystem: "docker"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
open-pull-requests-limit: 20
|
||||
@@ -0,0 +1,2 @@
|
||||
*.yaml
|
||||
*.yml
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"arrowParens": "always",
|
||||
"quoteProps": "as-needed",
|
||||
"bracketSpacing": true,
|
||||
"bracketSameLine": false
|
||||
}
|
||||
+253
@@ -0,0 +1,253 @@
|
||||
## [0.4.0] - 2025-12-29
|
||||
|
||||
### 🚀 Features
|
||||
|
||||
- *(session-cleanup)* Implement session expiration cleanup
|
||||
## [0.3.0] - 2025-12-28
|
||||
|
||||
### 🚀 Features
|
||||
|
||||
- Increase resource limits and improve readiness checks
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- *(deps)* Update dependency debug to v4.4.2
|
||||
- Downgrade debug package to version 4.4.1
|
||||
- *(deps)* Update dependency debug to v4.4.3
|
||||
- *(deps)* Update dependency nodemon to v3.1.11
|
||||
- *(deps)* Update dependency body-parser to v2.2.1
|
||||
- *(deps)* Update dependency express to v5.2.0
|
||||
- *(deps)* Update dependency express to v5.2.1
|
||||
- *(deps)* Update dependency jsonwebtoken to v9.0.3
|
||||
## [0.2.0] - 2025-06-29
|
||||
|
||||
### 🚀 Features
|
||||
|
||||
- *(ci)* Add default CI configuration for pipelines
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- *(deps)* Update dependency nodemon to v3.1.10
|
||||
- *(deps)* Update dependency debug to v4.4.1
|
||||
- *(deps)* Update dependency serve-favicon to v2.5.1
|
||||
- *(k8s)* Update ingress configuration for backend service
|
||||
## [0.1.5] - 2025-04-03
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- *(deps)* Update dependency body-parser to v2
|
||||
- *(deps)* Update dependency express to v5
|
||||
- Update CORS options for better compatibility
|
||||
|
||||
### ⚙️ Miscellaneous Tasks
|
||||
|
||||
- *(Dockerfile)* Update Node.js base image version
|
||||
## [0.1.4] - 2025-01-24
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- *(k8s)* Update labels to adhere to best practices
|
||||
## [0.1.3] - 2024-12-18
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- *(deps)* Pin dependencies
|
||||
- *(deps)* Update dependency express to v4.21.2
|
||||
- *(deps)* Update dependency debug to v4.4.0
|
||||
- *(deps)* Update dependency nodemon to v3.1.9
|
||||
|
||||
### 🚜 Refactor
|
||||
|
||||
- *(ci)* Remove unused Docker variables from config
|
||||
|
||||
### ⚙️ Miscellaneous Tasks
|
||||
|
||||
- Update renovate configuration to disable auth0mock updates
|
||||
- Remove Docker service from build stage configuration
|
||||
## [0.1.2] - 2024-10-19
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- *(deps)* Update dependency cookie-parser to v1.4.7
|
||||
- *(deps)* Update dependency express to v4.21.1
|
||||
|
||||
### ⚙️ Miscellaneous Tasks
|
||||
|
||||
- Update Dockerfile to remove warnings
|
||||
- Support issuer in openid-configuration
|
||||
## [0.1.1] - 2024-10-05
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- *(deps)* Update dependency nodemon to v3.1.1
|
||||
- *(deps)* Update dependency nodemon to v3.1.2
|
||||
- *(deps)* Update dependency debug to v4.3.5
|
||||
- *(deps)* Update dependency nodemon to v3.1.3
|
||||
- *(deps)* Update dependency nodemon to v3.1.4
|
||||
- *(deps)* Update dependency debug to v4.3.6
|
||||
- *(deps)* Update dependency debug to v4.3.7
|
||||
- *(deps)* Update dependency body-parser to v1.20.3
|
||||
- *(deps)* Update dependency express to v4.20.0
|
||||
- *(deps)* Update dependency express to v4.21.0
|
||||
- *(deps)* Update dependency nodemon to v3.1.5
|
||||
- *(deps)* Update dependency nodemon to v3.1.6
|
||||
- *(deps)* Update dependency nodemon to v3.1.7
|
||||
|
||||
### ⚙️ Miscellaneous Tasks
|
||||
|
||||
- Add release flow
|
||||
## [0.1.0] - 2024-04-08
|
||||
|
||||
### 🚀 Features
|
||||
|
||||
- Replace keystore handling with node-jose
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- *(deps)* Update dependency express to v4.19.1
|
||||
- *(deps)* Update dependency express to v4.19.2
|
||||
## [0.0.17] - 2024-03-11
|
||||
|
||||
### 🚀 Features
|
||||
|
||||
- Support patching of user info
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- *(deps)* Update dependency express to v4.18.3
|
||||
## [0.0.16] - 2023-06-01
|
||||
|
||||
### 🚀 Features
|
||||
|
||||
- Initial users store
|
||||
## [0.0.15] - 2023-05-31
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- Return empty array
|
||||
## [0.0.14] - 2023-05-31
|
||||
|
||||
### 🚀 Features
|
||||
|
||||
- Remember created users
|
||||
## [0.0.13] - 2023-05-02
|
||||
|
||||
### 🚀 Features
|
||||
|
||||
- Add name and email to id token
|
||||
## [0.0.12] - 2023-03-10
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- Remove session on logout
|
||||
## [0.0.11] - 2023-03-10
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- Update image name to correct location
|
||||
- Handle response mode query
|
||||
|
||||
### ⚙️ Miscellaneous Tasks
|
||||
|
||||
- Use Docker DinD version from variable
|
||||
- Change Dependabot rebase strategy
|
||||
- Format code and add prettier
|
||||
## [0.0.9] - 2022-04-28
|
||||
|
||||
### 🚀 Features
|
||||
|
||||
- Add support for client id and secret tokens
|
||||
## [0.0.8] - 2022-04-26
|
||||
|
||||
### 🚀 Features
|
||||
|
||||
- Add dummy-implementation of management API
|
||||
## [0.0.7] - 2022-04-26
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- Use correct return-variable
|
||||
## [0.0.6] - 2022-04-26
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- Make sure thumbPrint is a string
|
||||
|
||||
### 💼 Other
|
||||
|
||||
- *(deps)* Bump express from 4.17.3 to 4.18.0
|
||||
|
||||
### ⚙️ Miscellaneous Tasks
|
||||
|
||||
- Format code
|
||||
## [0.0.5] - 2022-04-26
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- Add custom claims to both id and access token
|
||||
## [0.0.4] - 2022-04-26
|
||||
|
||||
### 🚀 Features
|
||||
|
||||
- Add email custom claim
|
||||
## [0.0.3] - 2022-04-26
|
||||
|
||||
### 🚀 Features
|
||||
|
||||
- Add env-property for default issuer
|
||||
## [0.0.2] - 2022-04-25
|
||||
|
||||
### 💼 Other
|
||||
|
||||
- *(deps)* Bump node from 17 to 18
|
||||
|
||||
### ⚙️ Miscellaneous Tasks
|
||||
|
||||
- Change admin-handling
|
||||
## [0.0.1] - 2022-04-19
|
||||
|
||||
### 🚀 Features
|
||||
|
||||
- Initial commit
|
||||
- Updated to be compatible with Auth0 SPA which uses the 'Authorization Code Grant using Proof Key for Code Exchange (PKCE)' flow
|
||||
- Add ENV-property for setting admin-role
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- Use correct envs
|
||||
- Use commit rather than latest
|
||||
- Package.json & yarn.lock to reduce vulnerabilities
|
||||
- Package.json & yarn.lock to reduce vulnerabilities
|
||||
- Pipeline
|
||||
|
||||
### 💼 Other
|
||||
|
||||
- *(deps)* Bump nodemon from 2.0.14 to 2.0.15
|
||||
- *(deps)* Bump cookie-parser from 1.4.5 to 1.4.6
|
||||
- *(deps)* Bump debug from 4.3.2 to 4.3.3
|
||||
- *(deps)* Bump body-parser from 1.19.0 to 1.19.1
|
||||
- *(deps)* Bump express from 4.17.1 to 4.17.2
|
||||
- *(deps)* Bump node-forge from 0.10.0 to 1.0.0
|
||||
- *(deps)* Bump node-forge from 1.0.0 to 1.1.0
|
||||
- *(deps)* Bump node-forge from 1.1.0 to 1.2.0
|
||||
- *(deps)* Bump node-forge from 1.2.0 to 1.2.1
|
||||
- *(deps)* Bump body-parser from 1.19.1 to 1.19.2
|
||||
- *(deps)* Bump https-localhost from 4.7.0 to 4.7.1
|
||||
- *(deps)* Bump express from 4.17.2 to 4.17.3
|
||||
- *(deps)* Bump debug from 4.3.3 to 4.3.4
|
||||
- *(deps)* Bump node-forge from 1.2.1 to 1.3.0
|
||||
- *(deps)* Bump node-forge from 1.3.0 to 1.3.1
|
||||
- *(deps)* Bump body-parser from 1.19.2 to 1.20.0
|
||||
|
||||
### ⚙️ Miscellaneous Tasks
|
||||
|
||||
- Add triggering of acctest
|
||||
- Add artifacts
|
||||
- Update to latest build-tools
|
||||
- Update to latest build-tools
|
||||
- Add ingress
|
||||
- Add CI workflows
|
||||
- Use buildtools version from env
|
||||
- Add dependabot config
|
||||
- Remove dependabot-standalone
|
||||
- Cleanup and remove acctest triggering
|
||||
@@ -0,0 +1,83 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
auth0mock is a Node.js/Express application that simulates an Auth0 authentication server for local development. It provides OAuth 2.0 and OpenID Connect (OIDC) endpoints compatible with the Auth0 API, allowing developers to test authentication flows without connecting to the actual Auth0 service.
|
||||
|
||||
## Development Commands
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
yarn install
|
||||
|
||||
# Start production server (port 3333)
|
||||
yarn start
|
||||
|
||||
# Development with auto-reload (nodemon)
|
||||
yarn dev
|
||||
|
||||
# Format code
|
||||
yarn lintfix
|
||||
|
||||
# Check formatting
|
||||
yarn lint
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
This is a single-file Express application (`app.js`) that implements:
|
||||
|
||||
**Authentication Endpoints:**
|
||||
|
||||
- `POST /oauth/token` - Token exchange (OAuth 2.0 authorization code flow)
|
||||
- `GET /authorize` - Authorization endpoint with HTML login form
|
||||
- `POST /code` - Code generation for PKCE flow
|
||||
|
||||
**Discovery Endpoints:**
|
||||
|
||||
- `GET /.well-known/openid-configuration` - OIDC discovery document
|
||||
- `GET /.well-known/jwks.json` - JSON Web Key Set for token verification
|
||||
|
||||
**Management API (Auth0-compatible):**
|
||||
|
||||
- `GET /api/v2/users-by-email` - Get user by email
|
||||
- `POST /api/v2/users` - Create user
|
||||
- `PATCH /api/v2/users/:userid` - Update user
|
||||
- `POST /api/v2/tickets/password-change` - Password change ticket
|
||||
|
||||
**Key Implementation Details:**
|
||||
|
||||
- RSA 2048-bit key pair generated at startup using `node-jose`
|
||||
- In-memory session and user storage (not persistent)
|
||||
- PKCE support with code challenge verification
|
||||
- Custom claims for admin (`https://unbound.se/admin`) and email (`https://unbound.se/email`)
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Variable | Default | Purpose |
|
||||
| ------------ | -------------------------- | -------------------------------- |
|
||||
| `ISSUER` | `localhost:3333` | JWT issuer claim |
|
||||
| `AUDIENCE` | `https://generic-audience` | JWT audience claim |
|
||||
| `USERS_FILE` | `./users.json` | Path to initial users JSON file |
|
||||
| `DEBUG` | (unset) | Debug logging (`app*` to enable) |
|
||||
|
||||
## Initial Users
|
||||
|
||||
Create a `users.json` file to seed users on startup:
|
||||
|
||||
```json
|
||||
{
|
||||
"email@test.com": {
|
||||
"given_name": "John",
|
||||
"family_name": "Doe",
|
||||
"user_id": "auth0|email@test.com",
|
||||
"email": "email@test.com"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Integration with Shiny
|
||||
|
||||
This service is used for local development and acceptance testing of the Shiny platform. The gateway and frontend services are configured to accept tokens signed by this mock server when running locally.
|
||||
+6
-6
@@ -1,12 +1,12 @@
|
||||
FROM node:18
|
||||
ENV AUDIENCE "https://shiny.unbound.se"
|
||||
ENV ORIGIN_HOST "auth0mock"
|
||||
ENV ORIGIN "https://auth0mock:3333"
|
||||
FROM amd64/node:24.12.0@sha256:e8bb5aafe1964147c8344b1ea7698218e3675340407a07a14c49901df97455f6
|
||||
ENV AUDIENCE="https://shiny.unbound.se"
|
||||
ENV ORIGIN_HOST="auth0mock"
|
||||
ENV ORIGIN="https://auth0mock:3333"
|
||||
EXPOSE 3333
|
||||
WORKDIR /app
|
||||
ADD package.json yarn.lock /app/
|
||||
RUN yarn install --frozen-lockfile
|
||||
ADD app.js cert.js /app/
|
||||
ADD *.js /app/
|
||||
ADD public /app/public
|
||||
RUN mkdir -p /root/.config
|
||||
ENTRYPOINT yarn start
|
||||
ENTRYPOINT ["yarn", "start"]
|
||||
|
||||
@@ -3,47 +3,68 @@
|
||||
> This server helps you to simulate auth0 server locally. So, you are able to use the `/tokeninfo` endpoint to verify your token.
|
||||
|
||||
## Getting Started
|
||||
### Prerequisites
|
||||
* Install [Node.js](http://nodejs.org)
|
||||
* on OSX use [homebrew](http://brew.sh) `brew install node`
|
||||
* on Windows use [chocolatey](https://chocolatey.org/) `choco install nodejs`
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Install [Node.js](http://nodejs.org)
|
||||
- on OSX use [homebrew](http://brew.sh) `brew install node`
|
||||
- on Windows use [chocolatey](https://chocolatey.org/) `choco install nodejs`
|
||||
|
||||
## Installing
|
||||
* `fork` this repo
|
||||
* `clone` your fork
|
||||
* `npm install` to install all dependencies
|
||||
|
||||
- `fork` this repo
|
||||
- `clone` your fork
|
||||
- `npm install` to install all dependencies
|
||||
|
||||
## Running the app
|
||||
|
||||
After you have installed all dependencies you can now run the app.
|
||||
Run `npm start` to start a local server.
|
||||
The port will be displayed to you as `http://0.0.0.0:3333` (or if you prefer IPv6, if you're using `express` server, then it's `http://[::1]:3333/`).
|
||||
|
||||
## Initial users
|
||||
|
||||
Adding a JSON file with the following layout will populate the users store when starting:
|
||||
|
||||
```json
|
||||
{
|
||||
"email@test.com": {
|
||||
"given_name": "name",
|
||||
"family_name": "family",
|
||||
"user_id": "id"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
By default `./users.json` will be read but this can be overridden by setting the environment variable `USERS_FILE`.
|
||||
|
||||
## API Documentation
|
||||
|
||||
### `GET` /token/:username
|
||||
|
||||
Returns a token with the given user(username). This token can the be used by your application.
|
||||
|
||||
### `POST` /tokeninfo
|
||||
|
||||
Returns the data of the token like the username.
|
||||
|
||||
**Body**
|
||||
|
||||
```
|
||||
{
|
||||
"id_token": "your-token-kjasdf6ashasl..."
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Related Projects
|
||||
* [express-typescript-boilerplate](https://github.com/w3tecch/express-typescript-boilerplate) - Boilerplate for an restful express-apllication written in TypeScript
|
||||
* [express-graphql-typescript-boilerplate](https://github.com/w3tecch/express-graphql-typescript-boilerplate) - A starter kit for building amazing GraphQL API's with TypeScript and express by @w3tecch
|
||||
|
||||
- [express-typescript-boilerplate](https://github.com/w3tecch/express-typescript-boilerplate) - Boilerplate for an restful express-apllication written in TypeScript
|
||||
- [express-graphql-typescript-boilerplate](https://github.com/w3tecch/express-graphql-typescript-boilerplate) - A starter kit for building amazing GraphQL API's with TypeScript and express by @w3tecch
|
||||
|
||||
## License
|
||||
|
||||
[MIT](/LICENSE)
|
||||
|
||||
|
||||
---
|
||||
Made with ♥ by Gery Hirschfeld ([@GeryHirschfeld1](https://twitter.com/GeryHirschfeld1))
|
||||
|
||||
Made with ♥ by Gery Hirschfeld ([@GeryHirschfeld1](https://twitter.com/GeryHirschfeld1))
|
||||
|
||||
@@ -8,22 +8,46 @@ const Debug = require('debug')
|
||||
const path = require('path')
|
||||
const cors = require('cors')
|
||||
const bodyParser = require('body-parser')
|
||||
const jose = require('node-jose')
|
||||
const favicon = require('serve-favicon')
|
||||
const cert = require('./cert')
|
||||
const initialUsers = require('./users')
|
||||
|
||||
let issuer = process.env.ISSUER || 'localhost:3333'
|
||||
let jwksOrigin = `https://${issuer}/`
|
||||
const issuer = process.env.ISSUER || 'localhost:3333'
|
||||
const jwksOrigin = `https://${issuer}/`
|
||||
const audience = process.env.AUDIENCE || 'https://generic-audience'
|
||||
const adminCustomClaim = process.env.ADMIN_CUSTOM_CLAIM || 'https://unbound.se/admin'
|
||||
const emailCustomClaim = process.env.EMAIL_CUSTOM_CLAIM || 'https://unbound.se/email'
|
||||
const adminCustomClaim =
|
||||
process.env.ADMIN_CUSTOM_CLAIM || 'https://unbound.se/admin'
|
||||
const emailCustomClaim =
|
||||
process.env.EMAIL_CUSTOM_CLAIM || 'https://unbound.se/email'
|
||||
|
||||
const debug = Debug('app')
|
||||
|
||||
let { privateKey, certDer, thumbprint, exponent, modulus } = cert(jwksOrigin)
|
||||
|
||||
const keyStore = jose.JWK.createKeyStore()
|
||||
keyStore.generate('RSA', 2048, { alg: 'RS256', use: 'sig' })
|
||||
// let { privateKey, certDer, thumbprint, exponent, modulus } = cert(jwksOrigin)
|
||||
const users = initialUsers(process.env.USERS_FILE || './users.json')
|
||||
const sessions = {}
|
||||
const challenges = {}
|
||||
|
||||
// Session TTL in milliseconds (5 minutes)
|
||||
const SESSION_TTL_MS = 5 * 60 * 1000
|
||||
|
||||
// Periodically clean up old sessions to prevent memory leaks
|
||||
setInterval(() => {
|
||||
const now = Date.now()
|
||||
let cleaned = 0
|
||||
for (const [key, session] of Object.entries(sessions)) {
|
||||
if (session.createdAt && now - session.createdAt > SESSION_TTL_MS) {
|
||||
delete sessions[key]
|
||||
delete challenges[key]
|
||||
cleaned++
|
||||
}
|
||||
}
|
||||
if (cleaned > 0) {
|
||||
debug(`Cleaned up ${cleaned} expired sessions`)
|
||||
}
|
||||
}, 60000) // Run every minute
|
||||
|
||||
const corsOpts = (req, cb) => {
|
||||
cb(null, { origin: req.headers.origin })
|
||||
}
|
||||
@@ -37,74 +61,107 @@ const addCustomClaims = (email, customClaims, token) => {
|
||||
...claim
|
||||
}
|
||||
}, token)
|
||||
}
|
||||
|
||||
const signToken = async (token) => {
|
||||
const [key] = keyStore.all({ use: 'sig' })
|
||||
const opt = { compact: true, jwk: key, fields: { typ: 'jwt' } }
|
||||
return await jose.JWS.createSign(opt, key)
|
||||
.update(JSON.stringify(token))
|
||||
.final()
|
||||
}
|
||||
|
||||
// Configure our small auth0-mock-server
|
||||
app.options('*', cors(corsOpts))
|
||||
app
|
||||
.options('*all', cors(corsOpts))
|
||||
.use(cors())
|
||||
.use(bodyParser.json())
|
||||
.use(bodyParser.json({ strict: false }))
|
||||
.use(bodyParser.urlencoded({ extended: true }))
|
||||
.use(cookieParser())
|
||||
.use(express.static(`${__dirname}/public`))
|
||||
.use(favicon(path.join(__dirname, 'public', 'favicon.ico')))
|
||||
|
||||
// This route can be used to generate a valid jwt-token.
|
||||
app.post('/oauth/token', (req, res) => {
|
||||
const code = req.body.code
|
||||
const session = sessions[code]
|
||||
app.post('/oauth/token', async (req, res) => {
|
||||
const date = Math.floor(Date.now() / 1000)
|
||||
if (req.body.grant_type === 'client_credentials' && req.body.client_id) {
|
||||
const accessToken = await signToken({
|
||||
iss: jwksOrigin,
|
||||
aud: [audience],
|
||||
sub: 'auth0|management',
|
||||
iat: date,
|
||||
exp: date + 7200,
|
||||
azp: req.body.client_id
|
||||
})
|
||||
|
||||
let date = Math.floor(Date.now() / 1000)
|
||||
let accessToken = jwt.sign(Buffer.from(JSON.stringify(addCustomClaims(session.email, session.customClaims, {
|
||||
iss: jwksOrigin,
|
||||
aud: [audience],
|
||||
sub: 'auth0|' + session.email,
|
||||
iat: date,
|
||||
exp: date + 7200,
|
||||
azp: session.clientId
|
||||
}))), privateKey, {
|
||||
algorithm: 'RS256',
|
||||
keyid: thumbprint
|
||||
})
|
||||
const idToken = await signToken({
|
||||
iss: jwksOrigin,
|
||||
aud: req.body.client_id,
|
||||
sub: 'auth0|management',
|
||||
iat: date,
|
||||
exp: date + 7200,
|
||||
azp: req.body.client_id,
|
||||
name: 'Management API'
|
||||
})
|
||||
|
||||
let idToken = jwt.sign(Buffer.from(JSON.stringify(addCustomClaims(session.email, session.customClaims, {
|
||||
iss: jwksOrigin,
|
||||
aud: session.clientId,
|
||||
nonce: session.nonce,
|
||||
sub: 'auth0|' + session.email,
|
||||
iat: date,
|
||||
exp: date + 7200,
|
||||
azp: session.clientId,
|
||||
name: 'Example Person',
|
||||
picture: 'https://cdn.playbuzz.com/cdn/5458360f-32ea-460e-a707-1a2d26760558/70bda687-cb84-4756-8a44-8cf735ed87b3.jpg'
|
||||
}))), privateKey, {
|
||||
algorithm: 'RS256',
|
||||
keyid: thumbprint
|
||||
})
|
||||
debug('Signed token for management API')
|
||||
|
||||
debug('Signed token for ' + session.email)
|
||||
// res.json({ token });
|
||||
res.json({
|
||||
access_token: accessToken,
|
||||
id_token: idToken,
|
||||
scope: 'openid%20profile%20email',
|
||||
expires_in: 7200,
|
||||
token_type: 'Bearer'
|
||||
})
|
||||
} else if (req.body.code) {
|
||||
const code = req.body.code
|
||||
const session = sessions[code]
|
||||
const accessToken = await signToken(
|
||||
addCustomClaims(session.email, session.customClaims, {
|
||||
iss: jwksOrigin,
|
||||
aud: [audience],
|
||||
sub: 'auth0|' + session.email,
|
||||
iat: date,
|
||||
exp: date + 7200,
|
||||
azp: session.clientId
|
||||
})
|
||||
)
|
||||
|
||||
res.json({
|
||||
access_token: accessToken,
|
||||
id_token: idToken,
|
||||
scope: 'openid%20profile%20email',
|
||||
expires_in: 7200,
|
||||
token_type: 'Bearer'
|
||||
})
|
||||
})
|
||||
const idToken = await signToken(
|
||||
addCustomClaims(session.email, session.customClaims, {
|
||||
iss: jwksOrigin,
|
||||
aud: session.clientId,
|
||||
nonce: session.nonce,
|
||||
sub: 'auth0|' + session.email,
|
||||
iat: date,
|
||||
exp: date + 7200,
|
||||
azp: session.clientId,
|
||||
name: 'Example Person',
|
||||
given_name: 'Example',
|
||||
family_name: 'Person',
|
||||
email: session.email,
|
||||
picture:
|
||||
'https://cdn.playbuzz.com/cdn/5458360f-32ea-460e-a707-1a2d26760558/70bda687-cb84-4756-8a44-8cf735ed87b3.jpg'
|
||||
})
|
||||
)
|
||||
|
||||
// This route can be used to generate a valid jwt-token.
|
||||
app.get('/token/:email', (req, res) => {
|
||||
if (!req.params.email) {
|
||||
debug('No user was given!')
|
||||
return res.status(400).send('user is missing')
|
||||
debug('Signed token for ' + session.email)
|
||||
|
||||
// Clean up session and challenge after successful token exchange
|
||||
delete sessions[code]
|
||||
delete challenges[code]
|
||||
|
||||
res.json({
|
||||
access_token: accessToken,
|
||||
id_token: idToken,
|
||||
scope: 'openid%20profile%20email',
|
||||
expires_in: 7200,
|
||||
token_type: 'Bearer'
|
||||
})
|
||||
} else {
|
||||
res.status(401)
|
||||
res.send('Missing client_id or client_secret')
|
||||
}
|
||||
const token = jwt.sign({
|
||||
user_id: 'auth0|' + req.params.email
|
||||
}, privateKey)
|
||||
debug('Signed token for ' + req.params.email)
|
||||
res.json({ token })
|
||||
})
|
||||
|
||||
app.post('/code', (req, res) => {
|
||||
@@ -125,9 +182,12 @@ app.post('/code', (req, res) => {
|
||||
nonce: req.body.nonce,
|
||||
clientId: req.body.clientId,
|
||||
codeChallenge: req.body.codeChallenge,
|
||||
customClaims: [claim]
|
||||
customClaims: [claim],
|
||||
createdAt: Date.now()
|
||||
}
|
||||
res.redirect(`${req.body.redirect}?domain=${issuer}&code=${code}&state=${encodeURIComponent(state)}`)
|
||||
res.redirect(
|
||||
`${req.body.redirect}?code=${code}&state=${encodeURIComponent(state)}`
|
||||
)
|
||||
})
|
||||
|
||||
app.get('/authorize', (req, res) => {
|
||||
@@ -138,13 +198,33 @@ app.get('/authorize', (req, res) => {
|
||||
const codeChallenge = req.query.code_challenge
|
||||
const prompt = req.query.prompt
|
||||
const responseMode = req.query.response_mode
|
||||
if (responseMode === 'query') {
|
||||
const code = req.cookies['auth0']
|
||||
const session = sessions[code]
|
||||
if (session) {
|
||||
session.nonce = nonce
|
||||
session.state = state
|
||||
session.codeChallenge = codeChallenge
|
||||
session.createdAt = Date.now() // Refresh timestamp
|
||||
sessions[codeChallenge] = session
|
||||
// Clean up old session entry if different key
|
||||
if (code !== codeChallenge) {
|
||||
delete sessions[code]
|
||||
delete challenges[code]
|
||||
}
|
||||
res.redirect(`${redirect}?code=${codeChallenge}&state=${state}`)
|
||||
return
|
||||
}
|
||||
}
|
||||
if (prompt === 'none' && responseMode === 'web_message') {
|
||||
const code = req.cookies['auth0']
|
||||
const session = sessions[code]
|
||||
session.nonce = nonce
|
||||
session.state = state
|
||||
session.codeChallenge = codeChallenge
|
||||
res.send(`
|
||||
if (session) {
|
||||
session.nonce = nonce
|
||||
session.state = state
|
||||
session.codeChallenge = codeChallenge
|
||||
session.createdAt = Date.now() // Refresh timestamp
|
||||
res.send(`
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
@@ -162,13 +242,16 @@ app.get('/authorize', (req, res) => {
|
||||
</script>
|
||||
</body>
|
||||
</html>`)
|
||||
} else {
|
||||
res.cookie('auth0', codeChallenge, {
|
||||
sameSite: 'None',
|
||||
secure: true,
|
||||
httpOnly: true
|
||||
})
|
||||
res.send(`
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
res.cookie('auth0', codeChallenge, {
|
||||
sameSite: 'None',
|
||||
secure: true,
|
||||
httpOnly: true
|
||||
})
|
||||
res.send(`
|
||||
<html lang='en'>
|
||||
<head>
|
||||
<meta charset='utf-8'>
|
||||
@@ -209,35 +292,97 @@ app.get('/authorize', (req, res) => {
|
||||
</body>
|
||||
</html>
|
||||
`)
|
||||
}
|
||||
})
|
||||
|
||||
app.get('/userinfo', (req, res) => {
|
||||
res.contentType('application/json').send(JSON.stringify({ picture: 'https://cdn.playbuzz.com/cdn/5458360f-32ea-460e-a707-1a2d26760558/70bda687-cb84-4756-8a44-8cf735ed87b3.jpg' }))
|
||||
res.contentType('application/json').send(
|
||||
JSON.stringify({
|
||||
picture:
|
||||
'https://cdn.playbuzz.com/cdn/5458360f-32ea-460e-a707-1a2d26760558/70bda687-cb84-4756-8a44-8cf735ed87b3.jpg'
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
app.get('/v2/logout', (req, res) => {
|
||||
res.redirect(`${req.query.returnTo}?domain=${issuer}`)
|
||||
const code = req.cookies['auth0']
|
||||
const session = sessions[code]
|
||||
if (session) {
|
||||
delete sessions[code]
|
||||
}
|
||||
res.redirect(req.query.returnTo)
|
||||
})
|
||||
|
||||
app.get('/.well-known/openid-configuration', (req, res) => {
|
||||
debug('Fetching OpenID configuration')
|
||||
res.contentType('application/json').send(
|
||||
JSON.stringify({
|
||||
issuer: `${jwksOrigin}`,
|
||||
authorization_endpoint: `${jwksOrigin}authorize`,
|
||||
token_endpoint: `${jwksOrigin}oauth/token`,
|
||||
token_endpoint_auth_methods_supported: [
|
||||
'client_secret_basic',
|
||||
'private_key_jwt'
|
||||
],
|
||||
token_endpoint_auth_signing_alg_values_supported: ['RS256'],
|
||||
userinfo_endpoint: `${jwksOrigin}userinfo`,
|
||||
check_session_iframe: `${jwksOrigin}check_session`,
|
||||
end_session_endpoint: `${jwksOrigin}end_session`,
|
||||
jwks_uri: `${jwksOrigin}.well-known/jwks.json`,
|
||||
registration_endpoint: `${jwksOrigin}register`,
|
||||
scopes_supported: [
|
||||
'openid',
|
||||
'profile',
|
||||
'email',
|
||||
'address',
|
||||
'phone',
|
||||
'offline_access'
|
||||
],
|
||||
response_types_supported: [
|
||||
'code',
|
||||
'code id_token',
|
||||
'id_token',
|
||||
'id_token token'
|
||||
],
|
||||
acr_values_supported: [],
|
||||
subject_types_supported: ['public', 'pairwise'],
|
||||
userinfo_signing_alg_values_supported: ['RS256', 'ES256', 'HS256'],
|
||||
userinfo_encryption_alg_values_supported: ['RSA-OAEP-256', 'A128KW'],
|
||||
userinfo_encryption_enc_values_supported: ['A128CBC-HS256', 'A128GCM'],
|
||||
id_token_signing_alg_values_supported: ['RS256', 'ES256', 'HS256'],
|
||||
id_token_encryption_alg_values_supported: ['RSA-OAEP-256', 'A128KW'],
|
||||
id_token_encryption_enc_values_supported: ['A128CBC-HS256', 'A128GCM'],
|
||||
request_object_signing_alg_values_supported: ['none', 'RS256', 'ES256'],
|
||||
display_values_supported: ['page', 'popup'],
|
||||
claim_types_supported: ['normal', 'distributed'],
|
||||
claims_supported: [
|
||||
'sub',
|
||||
'iss',
|
||||
'auth_time',
|
||||
'acr',
|
||||
'name',
|
||||
'given_name',
|
||||
'family_name',
|
||||
'nickname',
|
||||
'profile',
|
||||
'picture',
|
||||
'website',
|
||||
'email',
|
||||
'email_verified',
|
||||
'locale',
|
||||
'zoneinfo',
|
||||
'https://unbound.se/email',
|
||||
'https://unbound.se/admin'
|
||||
],
|
||||
claims_parameter_supported: true,
|
||||
service_documentation: 'http://auth0/',
|
||||
ui_locales_supported: ['en-US']
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
app.get('/.well-known/jwks.json', (req, res) => {
|
||||
res
|
||||
.contentType('application/json')
|
||||
.send(JSON.stringify({
|
||||
keys: [
|
||||
{
|
||||
alg: 'RS256',
|
||||
// e: 'AQAB',
|
||||
e: exponent,
|
||||
kid: thumbprint,
|
||||
kty: 'RSA',
|
||||
n: modulus,
|
||||
use: 'sig',
|
||||
x5c: [certDer],
|
||||
x5t: thumbprint
|
||||
}
|
||||
]
|
||||
}))
|
||||
debug('Fetching JWKS')
|
||||
res.contentType('application/json').send(keyStore.toJSON())
|
||||
})
|
||||
|
||||
// This route returns the inside of a jwt-token. Your main application
|
||||
@@ -257,21 +402,59 @@ app.post('/tokeninfo', (req, res) => {
|
||||
}
|
||||
})
|
||||
|
||||
app.post('/issuer', (req, res) => {
|
||||
if (!req.body.issuer) {
|
||||
debug('No issuer given in the body!')
|
||||
return res.status(401).send('missing issuer')
|
||||
app.get('/api/v2/users-by-email', (req, res) => {
|
||||
const email = req.query.email
|
||||
console.log('users', users)
|
||||
const user = users[email]
|
||||
if (user === undefined) {
|
||||
res.json([])
|
||||
} else {
|
||||
res.json([user])
|
||||
}
|
||||
issuer = req.body.issuer
|
||||
jwksOrigin = `https://${issuer}/`
|
||||
const { privateKey: key, certDer: der, thumbPrint: thumb, exponent: exp, modulus: mod } = cert(jwksOrigin)
|
||||
privateKey = key
|
||||
certDer = der
|
||||
thumbprint = thumb
|
||||
exponent = exp
|
||||
modulus = mod
|
||||
debug('Issuer set to ' + req.body.issuer)
|
||||
res.send('ok')
|
||||
})
|
||||
|
||||
app.patch('/api/v2/users/:userid', (req, res) => {
|
||||
const email = req.params.userid.slice(6)
|
||||
console.log('patching user with id', email)
|
||||
const user = users[email]
|
||||
if (!user) {
|
||||
res.sendStatus(404)
|
||||
return
|
||||
}
|
||||
users[email] = {
|
||||
email: email,
|
||||
given_name: req.body.given_name || user.given_name,
|
||||
family_name: req.body.family_name || user.family_name,
|
||||
user_id: email,
|
||||
picture: req.body.picture || user.picture
|
||||
}
|
||||
res.json({
|
||||
user_id: `auth0|${email}`
|
||||
})
|
||||
})
|
||||
|
||||
app.post('/api/v2/users', (req, res) => {
|
||||
const email = req.body.email
|
||||
users[email] = {
|
||||
email: email,
|
||||
given_name: 'Given',
|
||||
family_name: 'Last',
|
||||
user_id: email
|
||||
}
|
||||
res.json({
|
||||
user_id: `auth0|${email}`
|
||||
})
|
||||
})
|
||||
|
||||
app.post('/api/v2/tickets/password-change', (req, res) => {
|
||||
res.json({
|
||||
ticket: `https://some-url`
|
||||
})
|
||||
})
|
||||
|
||||
app.use(function (req, res, next) {
|
||||
console.log('404', req.path)
|
||||
res.status(404).send('error: 404 Not Found ' + req.path)
|
||||
})
|
||||
|
||||
app.listen(3333, () => {
|
||||
|
||||
@@ -1,132 +0,0 @@
|
||||
const base64url = require('base64-url')
|
||||
const createHash = require('crypto').createHash
|
||||
const forge = require('node-forge')
|
||||
const NodeRSA = require('node-rsa')
|
||||
|
||||
const PRIVATE_KEY_PEM =
|
||||
'-----BEGIN RSA PRIVATE KEY-----\n' +
|
||||
'MIIEpAIBAAKCAQEApoocpO3bbUF6o8eyJlQCfwLahEsunWdVF++yOEyKu4Lp1j0m\n' +
|
||||
'2j/P7iHOtxBAkjdM2X2oW3qO1mR0sIFefqnm93g0q2nRuYEoS+W3o6X50wjOVm8f\n' +
|
||||
'r/tLqELzy5BoET0AQl7Axp1DNsb0HNOBcoIBt+xVY4I+k6uXJJJMzbgvahAgSLZ9\n' +
|
||||
'RW0Z0WT+dCHZpZUj0nLxNXIPdci65Bw6IognqXHP6AwKZXpT6jCzjzq9uyHxVcud\n' +
|
||||
'qw6j0kQw48/A5A6AN5fIVy1cKnd0sKdqRX1NUqVoiOrO4jaDB1IdLD+YmRE/JjOH\n' +
|
||||
'sWIMElYCPxKqnsNo6VCslGX/ziinArHhqRBrHwIDAQABAoIBAHAdmpsN5iLvafjI\n' +
|
||||
'f45+EBAhg6p8Uq102zx6CakNHniN8Y5hLL7RJtJRwDBNqKrGv93LUoQDRhXfGw+Y\n' +
|
||||
'iF0NVIhVTF/5pU8VPGOcCr0JB96ilwZpWRPIQW7NZAMu/GBeiMYls/IB/TXrSnv9\n' +
|
||||
'h6/nBfEkEXgkPqx7YA0m0L3NuV3U1lCY/LhBJY4Xvi0uRdqu3tTHXftehuPwC4UB\n' +
|
||||
'42eJTWv/qLeOlkCdUUV4f7+dNaES88Vdhj6lu/BusnNhvnwHQik4dNwzPCGeP8NV\n' +
|
||||
'5gaesWiNWFZuTURGKk1B65p5LzNPjsVT50RDuW8FnSZwIvNcohrX9ILPsmg/t0Kr\n' +
|
||||
'ozcOksECgYEA4XWOK4twx5RG162zveRHqU7H9RBWSz7/PzM9Eob9vx/tC/b1YqBR\n' +
|
||||
'VShk23vje19eNiYWAkxcpobIP4ek/0ZT8nHkJg8wl+J/hnXADcvwv2dKnoFnm5pn\n' +
|
||||
'rTBUKc8R3wrSlAV8XQAtdnxsfFa5AOQJ6WFVI9AdfH3Iw8XZk4gIIPMCgYEAvRlY\n' +
|
||||
'y80HnR3kwMOqY488V1qk41dmfNqa+YDL+zkPF1HhHI9VnK5BQuI7lyKJl984KwHu\n' +
|
||||
'0gbwx3Wp4XkD5JUboEpl5LnaLsjEWemjTaQWdvJHPd5wkJ0m/jRQ2YeT4g2gFu4y\n' +
|
||||
'Pi/pWkrzhnzQQVAmOdAm5Kj27LtDzp0lspw3uCUCgYEAw2YdvFGSgfZZW4147QeO\n' +
|
||||
'sAbON+9bysUjdMPUl10VR/LEgA0d6MdnFfX3S13Y7tDdlvJ1OrKxzcWcgaru7ism\n' +
|
||||
'kEXy5KVfiRNNUNx2gb6RvWEpA6zFfc9ZMXlkSAPlyjfX/1+tw/Bmdn0pjK2gk0wP\n' +
|
||||
'5wtrPameFInzWPD9O+a2nM8CgYBZ6UhgNs+M9B7FTQOiLQPa4R2PfwobCXIwef4D\n' +
|
||||
'KIE1bFgl1T02r2AWZi1BUkmr7ZXuVQ/xyx0HKbopm/mu4PruvxEtrPTB0/IQcleU\n' +
|
||||
'XhXUXqRjFXXePOrCaaubkqxNCn95B67aBLvmk8awxn3a4DocuQ0VIgWuT+gQwIWh\n' +
|
||||
'JEgWBQKBgQDKD+2Yh1/rUzu15lbPH0JSpozUinuFjePieR/4n+5CtEUxWJ2f0WeK\n' +
|
||||
's4XWWf2qgUccjpiGju2UR840mgWROoZ8BfSTd5tg1F7bo0HMgu2hu0RIRpZcRhsA\n' +
|
||||
'Cd0GrJvf1t0QIdDCXAy+RpgU1SLSq4Q6Lomc0WA5C5nBw9RKEUOV9A==\n' +
|
||||
'-----END RSA PRIVATE KEY-----\n'
|
||||
|
||||
const PUBLIC_KEY_PEM =
|
||||
'-----BEGIN PUBLIC KEY-----\n' +
|
||||
'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApoocpO3bbUF6o8eyJlQC\n' +
|
||||
'fwLahEsunWdVF++yOEyKu4Lp1j0m2j/P7iHOtxBAkjdM2X2oW3qO1mR0sIFefqnm\n' +
|
||||
'93g0q2nRuYEoS+W3o6X50wjOVm8fr/tLqELzy5BoET0AQl7Axp1DNsb0HNOBcoIB\n' +
|
||||
't+xVY4I+k6uXJJJMzbgvahAgSLZ9RW0Z0WT+dCHZpZUj0nLxNXIPdci65Bw6Iogn\n' +
|
||||
'qXHP6AwKZXpT6jCzjzq9uyHxVcudqw6j0kQw48/A5A6AN5fIVy1cKnd0sKdqRX1N\n' +
|
||||
'UqVoiOrO4jaDB1IdLD+YmRE/JjOHsWIMElYCPxKqnsNo6VCslGX/ziinArHhqRBr\n' +
|
||||
'HwIDAQAB\n' +
|
||||
'-----END PUBLIC KEY-----\n'
|
||||
|
||||
const createCertificate = ({
|
||||
publicKey,
|
||||
privateKey,
|
||||
jwksOrigin
|
||||
}) => {
|
||||
const cert = forge.pki.createCertificate()
|
||||
cert.publicKey = publicKey
|
||||
cert.serialNumber = '123'
|
||||
const attrs = [
|
||||
{
|
||||
name: 'commonName',
|
||||
value: `${jwksOrigin}`
|
||||
}
|
||||
]
|
||||
cert.validity.notBefore = new Date()
|
||||
cert.validity.notAfter = new Date()
|
||||
cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1)
|
||||
cert.setSubject(attrs)
|
||||
cert.setIssuer(attrs)
|
||||
cert.sign(privateKey)
|
||||
return forge.pki.certificateToPem(cert)
|
||||
}
|
||||
|
||||
const getCertThumbprint = (certificate) => {
|
||||
const shasum = createHash('sha1')
|
||||
const der = Buffer.from(certificate).toString('binary')
|
||||
shasum.update(der)
|
||||
return shasum.digest('base64')
|
||||
}
|
||||
|
||||
const createKeyPair = () => {
|
||||
const privateKey = forge.pki.privateKeyFromPem(PRIVATE_KEY_PEM)
|
||||
const publicKey = forge.pki.publicKeyFromPem(PUBLIC_KEY_PEM)
|
||||
return {
|
||||
privateKey,
|
||||
publicKey
|
||||
}
|
||||
}
|
||||
|
||||
const bnToB64 = (bn) => {
|
||||
let hex = BigInt(bn).toString(16)
|
||||
if (hex.length % 2) {
|
||||
hex = '0' + hex
|
||||
}
|
||||
|
||||
const bin = []
|
||||
let i = 0
|
||||
let d
|
||||
let b
|
||||
while (i < hex.length) {
|
||||
d = parseInt(hex.slice(i, i + 2), 16)
|
||||
b = String.fromCharCode(d)
|
||||
bin.push(b)
|
||||
i += 2
|
||||
}
|
||||
|
||||
return Buffer.from(bin.join(''), 'binary').toString('base64')
|
||||
}
|
||||
|
||||
const setup = (jwksOrigin) => {
|
||||
const { privateKey, publicKey } = createKeyPair()
|
||||
const certPem = createCertificate({
|
||||
jwksOrigin,
|
||||
privateKey,
|
||||
publicKey
|
||||
})
|
||||
const certDer = forge.util.encode64(
|
||||
forge.asn1
|
||||
.toDer(forge.pki.certificateToAsn1(forge.pki.certificateFromPem(certPem)))
|
||||
.getBytes()
|
||||
)
|
||||
const thumbprint = base64url.encode(getCertThumbprint(certDer))
|
||||
|
||||
const helperKey = new NodeRSA()
|
||||
helperKey.importKey(forge.pki.privateKeyToPem(privateKey))
|
||||
const { n: modulus, e: exponent } = helperKey.exportKey('components')
|
||||
|
||||
return {
|
||||
privateKey: forge.pki.privateKeyToPem(privateKey),
|
||||
certDer: certDer,
|
||||
thumbPrint: thumbprint.toString(),
|
||||
exponent: bnToB64(exponent),
|
||||
modulus: modulus.toString('base64')
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = setup
|
||||
+14
-8
@@ -6,21 +6,21 @@ spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: auth0mock
|
||||
app.kubernetes.io/name: auth0mock
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: auth0mock
|
||||
app.kubernetes.io/name: auth0mock
|
||||
spec:
|
||||
containers:
|
||||
- name: auth0mock
|
||||
image: registry.gitlab.com/unboundsoftware/shiny/auth0mock:${COMMIT}
|
||||
image: registry.gitlab.com/unboundsoftware/auth0mock:${COMMIT}
|
||||
imagePullPolicy: "IfNotPresent"
|
||||
resources:
|
||||
requests:
|
||||
memory: 200Mi
|
||||
memory: 256Mi
|
||||
limits:
|
||||
memory: 300Mi
|
||||
memory: 512Mi
|
||||
ports:
|
||||
- containerPort: 3333
|
||||
env:
|
||||
@@ -29,17 +29,23 @@ spec:
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
port: 3333
|
||||
path: .well-known/jwks.json
|
||||
path: /.well-known/jwks.json
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
port: 3333
|
||||
path: /.well-known/jwks.json
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 30
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: auth0mock
|
||||
labels:
|
||||
app: auth0mock
|
||||
app.kubernetes.io/name: auth0mock
|
||||
spec:
|
||||
ports:
|
||||
- port: 3333
|
||||
selector:
|
||||
app: auth0mock
|
||||
app.kubernetes.io/name: auth0mock
|
||||
type: ClusterIP
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
apiVersion: networking.k8s.io/v1beta1
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: auth0-ingress
|
||||
@@ -11,9 +11,12 @@ spec:
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
serviceName: auth0mock
|
||||
servicePort: 3333
|
||||
service:
|
||||
name: auth0mock
|
||||
port:
|
||||
number: 3333
|
||||
tls:
|
||||
- hosts:
|
||||
- auth0
|
||||
|
||||
+17
-14
@@ -6,23 +6,26 @@
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"dev": "nodemon ./app.js",
|
||||
"start": "node ./app.js"
|
||||
"start": "node ./app.js",
|
||||
"lint:prettier": "prettier --check .",
|
||||
"lint": "yarn lint:prettier",
|
||||
"lintfix": "prettier --write --list-different ."
|
||||
},
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"base64-url": "^2.3.3",
|
||||
"body-parser": "^1.20.0",
|
||||
"buffer": "^6.0.3",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"cors": "^2.8.3",
|
||||
"debug": "^4.3.4",
|
||||
"express": "^4.18.0",
|
||||
"https-localhost": "^4.7.1",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"node-forge": "^1.3.1",
|
||||
"node-rsa": "^1.1.1",
|
||||
"nodemon": "^2.0.15",
|
||||
"serve-favicon": "^2.4.2"
|
||||
"body-parser": "2.2.1",
|
||||
"cookie-parser": "1.4.7",
|
||||
"cors": "2.8.5",
|
||||
"debug": "4.4.3",
|
||||
"express": "5.2.1",
|
||||
"https-localhost": "4.7.1",
|
||||
"jsonwebtoken": "9.0.3",
|
||||
"node-jose": "2.2.0",
|
||||
"nodemon": "3.1.11",
|
||||
"serve-favicon": "2.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier": "3.7.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:recommended"
|
||||
],
|
||||
"packageRules": [
|
||||
{
|
||||
"matchManagers": [
|
||||
"kubernetes"
|
||||
],
|
||||
"matchPackageNames": [
|
||||
"registry.gitlab.com/unboundsoftware/auth0mock"
|
||||
],
|
||||
"enabled": false
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
const fs = require('fs')
|
||||
|
||||
const setup = (usersFile) => {
|
||||
let users = {}
|
||||
if (fs.existsSync(usersFile)) {
|
||||
console.log(`initial users file "${usersFile}" exists, reading`)
|
||||
const read = fs.readFileSync(usersFile, { encoding: 'utf8', flag: 'r' })
|
||||
users = JSON.parse(read)
|
||||
for (let key of Object.keys(users)) {
|
||||
users[key] = { ...users[key], email: key }
|
||||
}
|
||||
console.log('users:', users)
|
||||
} else {
|
||||
console.log(`initial users file "${usersFile}" missing`)
|
||||
}
|
||||
return users
|
||||
}
|
||||
module.exports = setup
|
||||
Reference in New Issue
Block a user