Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | |||
| 37ac46759d | |||
| 424e500d6b | |||
| 9539d9b38d | |||
| a9ea2dace4 | |||
| aad18ad000 | |||
| 68d3f0df82 |
+1
-1
@@ -16,7 +16,7 @@ image: buildtool/build-tools:${BUILDTOOLS_VERSION}
|
|||||||
build:
|
build:
|
||||||
stage: build
|
stage: build
|
||||||
services:
|
services:
|
||||||
- docker:dind
|
- docker:${DOCKER_DIND_VERSION}
|
||||||
script:
|
script:
|
||||||
- build
|
- build
|
||||||
- push
|
- push
|
||||||
|
|||||||
@@ -10,8 +10,10 @@ updates:
|
|||||||
schedule:
|
schedule:
|
||||||
interval: "daily"
|
interval: "daily"
|
||||||
open-pull-requests-limit: 20
|
open-pull-requests-limit: 20
|
||||||
|
rebase-strategy: none
|
||||||
- package-ecosystem: "docker"
|
- package-ecosystem: "docker"
|
||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "daily"
|
interval: "daily"
|
||||||
open-pull-requests-limit: 20
|
open-pull-requests-limit: 20
|
||||||
|
rebase-strategy: none
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
FROM node:18
|
FROM node:20
|
||||||
ENV AUDIENCE "https://shiny.unbound.se"
|
ENV AUDIENCE "https://shiny.unbound.se"
|
||||||
ENV ORIGIN_HOST "auth0mock"
|
ENV ORIGIN_HOST "auth0mock"
|
||||||
ENV ORIGIN "https://auth0mock:3333"
|
ENV ORIGIN "https://auth0mock:3333"
|
||||||
|
|||||||
@@ -3,47 +3,52 @@
|
|||||||
> This server helps you to simulate auth0 server locally. So, you are able to use the `/tokeninfo` endpoint to verify your token.
|
> This server helps you to simulate auth0 server locally. So, you are able to use the `/tokeninfo` endpoint to verify your token.
|
||||||
|
|
||||||
## Getting Started
|
## 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
|
## Installing
|
||||||
* `fork` this repo
|
|
||||||
* `clone` your fork
|
- `fork` this repo
|
||||||
* `npm install` to install all dependencies
|
- `clone` your fork
|
||||||
|
- `npm install` to install all dependencies
|
||||||
|
|
||||||
## Running the app
|
## Running the app
|
||||||
|
|
||||||
After you have installed all dependencies you can now run the app.
|
After you have installed all dependencies you can now run the app.
|
||||||
Run `npm start` to start a local server.
|
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/`).
|
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/`).
|
||||||
|
|
||||||
|
|
||||||
## API Documentation
|
## API Documentation
|
||||||
|
|
||||||
### `GET` /token/:username
|
### `GET` /token/:username
|
||||||
|
|
||||||
Returns a token with the given user(username). This token can the be used by your application.
|
Returns a token with the given user(username). This token can the be used by your application.
|
||||||
|
|
||||||
### `POST` /tokeninfo
|
### `POST` /tokeninfo
|
||||||
|
|
||||||
Returns the data of the token like the username.
|
Returns the data of the token like the username.
|
||||||
|
|
||||||
**Body**
|
**Body**
|
||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
"id_token": "your-token-kjasdf6ashasl..."
|
"id_token": "your-token-kjasdf6ashasl..."
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Related Projects
|
## 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
|
## License
|
||||||
|
|
||||||
[MIT](/LICENSE)
|
[MIT](/LICENSE)
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
Made with ♥ by Gery Hirschfeld ([@GeryHirschfeld1](https://twitter.com/GeryHirschfeld1))
|
|
||||||
|
Made with ♥ by Gery Hirschfeld ([@GeryHirschfeld1](https://twitter.com/GeryHirschfeld1))
|
||||||
|
|||||||
@@ -14,12 +14,16 @@ const cert = require('./cert')
|
|||||||
let issuer = process.env.ISSUER || 'localhost:3333'
|
let issuer = process.env.ISSUER || 'localhost:3333'
|
||||||
let jwksOrigin = `https://${issuer}/`
|
let jwksOrigin = `https://${issuer}/`
|
||||||
const audience = process.env.AUDIENCE || 'https://generic-audience'
|
const audience = process.env.AUDIENCE || 'https://generic-audience'
|
||||||
const adminCustomClaim = process.env.ADMIN_CUSTOM_CLAIM || 'https://unbound.se/admin'
|
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')
|
const debug = Debug('app')
|
||||||
|
|
||||||
let { privateKey, certDer, thumbprint, exponent, modulus } = cert(jwksOrigin)
|
let { privateKey, certDer, thumbprint, exponent, modulus } = cert(jwksOrigin)
|
||||||
|
|
||||||
|
const users = {}
|
||||||
const sessions = {}
|
const sessions = {}
|
||||||
const challenges = {}
|
const challenges = {}
|
||||||
|
|
||||||
@@ -27,10 +31,29 @@ const corsOpts = (req, cb) => {
|
|||||||
cb(null, { origin: req.headers.origin })
|
cb(null, { origin: req.headers.origin })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const addCustomClaims = (email, customClaims, token) => {
|
||||||
|
const emailClaim = {}
|
||||||
|
emailClaim[emailCustomClaim] = email
|
||||||
|
return [...customClaims, emailClaim].reduce((acc, claim) => {
|
||||||
|
return {
|
||||||
|
...acc,
|
||||||
|
...claim
|
||||||
|
}
|
||||||
|
}, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
const signToken = (token) => {
|
||||||
|
return jwt.sign(Buffer.from(JSON.stringify(token)), privateKey, {
|
||||||
|
algorithm: 'RS256',
|
||||||
|
keyid: thumbprint
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Configure our small auth0-mock-server
|
// Configure our small auth0-mock-server
|
||||||
app.options('*', cors(corsOpts))
|
app
|
||||||
|
.options('*', cors(corsOpts))
|
||||||
.use(cors())
|
.use(cors())
|
||||||
.use(bodyParser.json())
|
.use(bodyParser.json({ strict: false }))
|
||||||
.use(bodyParser.urlencoded({ extended: true }))
|
.use(bodyParser.urlencoded({ extended: true }))
|
||||||
.use(cookieParser())
|
.use(cookieParser())
|
||||||
.use(express.static(`${__dirname}/public`))
|
.use(express.static(`${__dirname}/public`))
|
||||||
@@ -38,53 +61,81 @@ app.options('*', cors(corsOpts))
|
|||||||
|
|
||||||
// This route can be used to generate a valid jwt-token.
|
// This route can be used to generate a valid jwt-token.
|
||||||
app.post('/oauth/token', (req, res) => {
|
app.post('/oauth/token', (req, res) => {
|
||||||
const code = req.body.code
|
|
||||||
const session = sessions[code]
|
|
||||||
|
|
||||||
let date = Math.floor(Date.now() / 1000)
|
let date = Math.floor(Date.now() / 1000)
|
||||||
let accessToken = jwt.sign(Buffer.from(JSON.stringify({
|
if (req.body.grant_type === 'client_credentials' && req.body.client_id) {
|
||||||
iss: jwksOrigin,
|
let accessToken = signToken({
|
||||||
aud: [audience],
|
iss: jwksOrigin,
|
||||||
sub: 'auth0|' + session.email,
|
aud: [audience],
|
||||||
iat: date,
|
sub: 'auth0|management',
|
||||||
exp: date + 7200,
|
iat: date,
|
||||||
azp: session.clientId
|
exp: date + 7200,
|
||||||
})), privateKey, {
|
azp: req.body.client_id
|
||||||
algorithm: 'RS256',
|
})
|
||||||
keyid: thumbprint
|
|
||||||
})
|
|
||||||
|
|
||||||
const token = session.customClaims.reduce((acc, claim) => {
|
let idToken = signToken({
|
||||||
return {
|
iss: jwksOrigin,
|
||||||
...acc,
|
aud: req.body.client_id,
|
||||||
...claim
|
sub: 'auth0|management',
|
||||||
}
|
iat: date,
|
||||||
}, {
|
exp: date + 7200,
|
||||||
iss: jwksOrigin,
|
azp: req.body.client_id,
|
||||||
aud: session.clientId,
|
name: 'Management API'
|
||||||
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'
|
|
||||||
})
|
|
||||||
let idToken = jwt.sign(Buffer.from(JSON.stringify(token)), privateKey, {
|
|
||||||
algorithm: 'RS256',
|
|
||||||
keyid: thumbprint
|
|
||||||
})
|
|
||||||
|
|
||||||
debug('Signed token for ' + session.email)
|
debug('Signed token for management API')
|
||||||
// res.json({ token });
|
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
access_token: accessToken,
|
access_token: accessToken,
|
||||||
id_token: idToken,
|
id_token: idToken,
|
||||||
scope: 'openid%20profile%20email',
|
scope: 'openid%20profile%20email',
|
||||||
expires_in: 7200,
|
expires_in: 7200,
|
||||||
token_type: 'Bearer'
|
token_type: 'Bearer'
|
||||||
})
|
})
|
||||||
|
} else if (req.body.code) {
|
||||||
|
const code = req.body.code
|
||||||
|
const session = sessions[code]
|
||||||
|
let accessToken = signToken(
|
||||||
|
addCustomClaims(session.email, session.customClaims, {
|
||||||
|
iss: jwksOrigin,
|
||||||
|
aud: [audience],
|
||||||
|
sub: 'auth0|' + session.email,
|
||||||
|
iat: date,
|
||||||
|
exp: date + 7200,
|
||||||
|
azp: session.clientId
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
let idToken = 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'
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
debug('Signed token for ' + session.email)
|
||||||
|
|
||||||
|
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')
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// This route can be used to generate a valid jwt-token.
|
// This route can be used to generate a valid jwt-token.
|
||||||
@@ -93,9 +144,12 @@ app.get('/token/:email', (req, res) => {
|
|||||||
debug('No user was given!')
|
debug('No user was given!')
|
||||||
return res.status(400).send('user is missing')
|
return res.status(400).send('user is missing')
|
||||||
}
|
}
|
||||||
const token = jwt.sign({
|
const token = jwt.sign(
|
||||||
user_id: 'auth0|' + req.params.email
|
{
|
||||||
}, privateKey)
|
user_id: 'auth0|' + req.params.email
|
||||||
|
},
|
||||||
|
privateKey
|
||||||
|
)
|
||||||
debug('Signed token for ' + req.params.email)
|
debug('Signed token for ' + req.params.email)
|
||||||
res.json({ token })
|
res.json({ token })
|
||||||
})
|
})
|
||||||
@@ -120,7 +174,9 @@ app.post('/code', (req, res) => {
|
|||||||
codeChallenge: req.body.codeChallenge,
|
codeChallenge: req.body.codeChallenge,
|
||||||
customClaims: [claim]
|
customClaims: [claim]
|
||||||
}
|
}
|
||||||
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) => {
|
app.get('/authorize', (req, res) => {
|
||||||
@@ -131,13 +187,26 @@ app.get('/authorize', (req, res) => {
|
|||||||
const codeChallenge = req.query.code_challenge
|
const codeChallenge = req.query.code_challenge
|
||||||
const prompt = req.query.prompt
|
const prompt = req.query.prompt
|
||||||
const responseMode = req.query.response_mode
|
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
|
||||||
|
sessions[codeChallenge] = session
|
||||||
|
res.redirect(`${redirect}?code=${codeChallenge}&state=${state}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
if (prompt === 'none' && responseMode === 'web_message') {
|
if (prompt === 'none' && responseMode === 'web_message') {
|
||||||
const code = req.cookies['auth0']
|
const code = req.cookies['auth0']
|
||||||
const session = sessions[code]
|
const session = sessions[code]
|
||||||
session.nonce = nonce
|
if (session) {
|
||||||
session.state = state
|
session.nonce = nonce
|
||||||
session.codeChallenge = codeChallenge
|
session.state = state
|
||||||
res.send(`
|
session.codeChallenge = codeChallenge
|
||||||
|
res.send(`
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<body>
|
<body>
|
||||||
@@ -155,13 +224,16 @@ app.get('/authorize', (req, res) => {
|
|||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>`)
|
</html>`)
|
||||||
} else {
|
return
|
||||||
res.cookie('auth0', codeChallenge, {
|
}
|
||||||
sameSite: 'None',
|
}
|
||||||
secure: true,
|
|
||||||
httpOnly: true
|
res.cookie('auth0', codeChallenge, {
|
||||||
})
|
sameSite: 'None',
|
||||||
res.send(`
|
secure: true,
|
||||||
|
httpOnly: true
|
||||||
|
})
|
||||||
|
res.send(`
|
||||||
<html lang='en'>
|
<html lang='en'>
|
||||||
<head>
|
<head>
|
||||||
<meta charset='utf-8'>
|
<meta charset='utf-8'>
|
||||||
@@ -202,21 +274,29 @@ app.get('/authorize', (req, res) => {
|
|||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
`)
|
`)
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
app.get('/userinfo', (req, res) => {
|
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) => {
|
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/jwks.json', (req, res) => {
|
app.get('/.well-known/jwks.json', (req, res) => {
|
||||||
res
|
res.contentType('application/json').send(
|
||||||
.contentType('application/json')
|
JSON.stringify({
|
||||||
.send(JSON.stringify({
|
|
||||||
keys: [
|
keys: [
|
||||||
{
|
{
|
||||||
alg: 'RS256',
|
alg: 'RS256',
|
||||||
@@ -230,7 +310,8 @@ app.get('/.well-known/jwks.json', (req, res) => {
|
|||||||
x5t: thumbprint
|
x5t: thumbprint
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}))
|
})
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
// This route returns the inside of a jwt-token. Your main application
|
// This route returns the inside of a jwt-token. Your main application
|
||||||
@@ -257,7 +338,13 @@ app.post('/issuer', (req, res) => {
|
|||||||
}
|
}
|
||||||
issuer = req.body.issuer
|
issuer = req.body.issuer
|
||||||
jwksOrigin = `https://${issuer}/`
|
jwksOrigin = `https://${issuer}/`
|
||||||
const { privateKey: key, certDer: der, thumbPrint: thumb, exponent: exp, modulus: mod } = cert(jwksOrigin)
|
const {
|
||||||
|
privateKey: key,
|
||||||
|
certDer: der,
|
||||||
|
thumbprint: thumb,
|
||||||
|
exponent: exp,
|
||||||
|
modulus: mod
|
||||||
|
} = cert(jwksOrigin)
|
||||||
privateKey = key
|
privateKey = key
|
||||||
certDer = der
|
certDer = der
|
||||||
thumbprint = thumb
|
thumbprint = thumb
|
||||||
@@ -267,6 +354,43 @@ app.post('/issuer', (req, res) => {
|
|||||||
res.send('ok')
|
res.send('ok')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
app.get('/api/v2/users-by-email', (req, res) => {
|
||||||
|
const email = req.query.email
|
||||||
|
|
||||||
|
const user = users[email]
|
||||||
|
if (user === undefined) {
|
||||||
|
res.json([])
|
||||||
|
} else {
|
||||||
|
res.json([
|
||||||
|
user
|
||||||
|
])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
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, () => {
|
app.listen(3333, () => {
|
||||||
debug('Auth0-Mock-Server listening on port 3333!')
|
debug('Auth0-Mock-Server listening on port 3333!')
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,132 +1,128 @@
|
|||||||
const base64url = require('base64-url');
|
const base64url = require('base64-url')
|
||||||
const createHash = require('crypto').createHash;
|
const createHash = require('crypto').createHash
|
||||||
const forge = require('node-forge');
|
const forge = require('node-forge')
|
||||||
const NodeRSA = require('node-rsa');
|
const NodeRSA = require('node-rsa')
|
||||||
|
|
||||||
const PRIVATE_KEY_PEM =
|
const PRIVATE_KEY_PEM =
|
||||||
'-----BEGIN RSA PRIVATE KEY-----\n' +
|
'-----BEGIN RSA PRIVATE KEY-----\n' +
|
||||||
'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\n' +
|
||||||
'-----END RSA PRIVATE KEY-----\n';
|
'-----END RSA PRIVATE KEY-----\n'
|
||||||
|
|
||||||
const PUBLIC_KEY_PEM =
|
const PUBLIC_KEY_PEM =
|
||||||
'-----BEGIN PUBLIC KEY-----\n' +
|
'-----BEGIN PUBLIC KEY-----\n' +
|
||||||
'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApoocpO3bbUF6o8eyJlQC\n' +
|
'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApoocpO3bbUF6o8eyJlQC\n' +
|
||||||
'fwLahEsunWdVF++yOEyKu4Lp1j0m2j/P7iHOtxBAkjdM2X2oW3qO1mR0sIFefqnm\n' +
|
'fwLahEsunWdVF++yOEyKu4Lp1j0m2j/P7iHOtxBAkjdM2X2oW3qO1mR0sIFefqnm\n' +
|
||||||
'93g0q2nRuYEoS+W3o6X50wjOVm8fr/tLqELzy5BoET0AQl7Axp1DNsb0HNOBcoIB\n' +
|
'93g0q2nRuYEoS+W3o6X50wjOVm8fr/tLqELzy5BoET0AQl7Axp1DNsb0HNOBcoIB\n' +
|
||||||
't+xVY4I+k6uXJJJMzbgvahAgSLZ9RW0Z0WT+dCHZpZUj0nLxNXIPdci65Bw6Iogn\n' +
|
't+xVY4I+k6uXJJJMzbgvahAgSLZ9RW0Z0WT+dCHZpZUj0nLxNXIPdci65Bw6Iogn\n' +
|
||||||
'qXHP6AwKZXpT6jCzjzq9uyHxVcudqw6j0kQw48/A5A6AN5fIVy1cKnd0sKdqRX1N\n' +
|
'qXHP6AwKZXpT6jCzjzq9uyHxVcudqw6j0kQw48/A5A6AN5fIVy1cKnd0sKdqRX1N\n' +
|
||||||
'UqVoiOrO4jaDB1IdLD+YmRE/JjOHsWIMElYCPxKqnsNo6VCslGX/ziinArHhqRBr\n' +
|
'UqVoiOrO4jaDB1IdLD+YmRE/JjOHsWIMElYCPxKqnsNo6VCslGX/ziinArHhqRBr\n' +
|
||||||
'HwIDAQAB\n' +
|
'HwIDAQAB\n' +
|
||||||
'-----END PUBLIC KEY-----\n';
|
'-----END PUBLIC KEY-----\n'
|
||||||
|
|
||||||
const createCertificate = ({
|
const createCertificate = ({ publicKey, privateKey, jwksOrigin }) => {
|
||||||
publicKey,
|
const cert = forge.pki.createCertificate()
|
||||||
privateKey,
|
cert.publicKey = publicKey
|
||||||
jwksOrigin,
|
cert.serialNumber = '123'
|
||||||
}) => {
|
const attrs = [
|
||||||
const cert = forge.pki.createCertificate();
|
{
|
||||||
cert.publicKey = publicKey;
|
name: 'commonName',
|
||||||
cert.serialNumber = '123';
|
value: `${jwksOrigin}`
|
||||||
const attrs = [
|
}
|
||||||
{
|
]
|
||||||
name: 'commonName',
|
cert.validity.notBefore = new Date()
|
||||||
value: `${jwksOrigin}`,
|
cert.validity.notAfter = new Date()
|
||||||
},
|
cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1)
|
||||||
];
|
cert.setSubject(attrs)
|
||||||
cert.validity.notBefore = new Date();
|
cert.setIssuer(attrs)
|
||||||
cert.validity.notAfter = new Date();
|
cert.sign(privateKey)
|
||||||
cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1);
|
return forge.pki.certificateToPem(cert)
|
||||||
cert.setSubject(attrs);
|
}
|
||||||
cert.setIssuer(attrs);
|
|
||||||
cert.sign(privateKey);
|
|
||||||
return forge.pki.certificateToPem(cert)
|
|
||||||
};
|
|
||||||
|
|
||||||
const getCertThumbprint = (certificate) => {
|
const getCertThumbprint = (certificate) => {
|
||||||
const shasum = createHash('sha1');
|
const shasum = createHash('sha1')
|
||||||
const der = Buffer.from(certificate).toString('binary');
|
const der = Buffer.from(certificate).toString('binary')
|
||||||
shasum.update(der);
|
shasum.update(der)
|
||||||
return shasum.digest('base64')
|
return shasum.digest('base64')
|
||||||
};
|
}
|
||||||
|
|
||||||
const createKeyPair = () => {
|
const createKeyPair = () => {
|
||||||
const privateKey = forge.pki.privateKeyFromPem(PRIVATE_KEY_PEM);
|
const privateKey = forge.pki.privateKeyFromPem(PRIVATE_KEY_PEM)
|
||||||
const publicKey = forge.pki.publicKeyFromPem(PUBLIC_KEY_PEM);
|
const publicKey = forge.pki.publicKeyFromPem(PUBLIC_KEY_PEM)
|
||||||
return {
|
return {
|
||||||
privateKey,
|
privateKey,
|
||||||
publicKey,
|
publicKey
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
const bnToB64 = (bn) => {
|
const bnToB64 = (bn) => {
|
||||||
let hex = BigInt(bn).toString(16);
|
let hex = BigInt(bn).toString(16)
|
||||||
if (hex.length % 2) {
|
if (hex.length % 2) {
|
||||||
hex = '0' + hex;
|
hex = '0' + hex
|
||||||
}
|
}
|
||||||
|
|
||||||
const bin = [];
|
const bin = []
|
||||||
let i = 0;
|
let i = 0
|
||||||
let d;
|
let d
|
||||||
let b;
|
let b
|
||||||
while (i < hex.length) {
|
while (i < hex.length) {
|
||||||
d = parseInt(hex.slice(i, i + 2), 16);
|
d = parseInt(hex.slice(i, i + 2), 16)
|
||||||
b = String.fromCharCode(d);
|
b = String.fromCharCode(d)
|
||||||
bin.push(b);
|
bin.push(b)
|
||||||
i += 2;
|
i += 2
|
||||||
}
|
}
|
||||||
|
|
||||||
return Buffer.from(bin.join(''), 'binary').toString('base64');
|
return Buffer.from(bin.join(''), 'binary').toString('base64')
|
||||||
};
|
}
|
||||||
|
|
||||||
const setup = (jwksOrigin) => {
|
const setup = (jwksOrigin) => {
|
||||||
const {privateKey, publicKey} = createKeyPair();
|
const { privateKey, publicKey } = createKeyPair()
|
||||||
const certPem = createCertificate({
|
const certPem = createCertificate({
|
||||||
jwksOrigin,
|
jwksOrigin,
|
||||||
privateKey,
|
privateKey,
|
||||||
publicKey,
|
publicKey
|
||||||
});
|
})
|
||||||
const certDer = forge.util.encode64(
|
const certDer = forge.util.encode64(
|
||||||
forge.asn1
|
forge.asn1
|
||||||
.toDer(forge.pki.certificateToAsn1(forge.pki.certificateFromPem(certPem)))
|
.toDer(forge.pki.certificateToAsn1(forge.pki.certificateFromPem(certPem)))
|
||||||
.getBytes()
|
.getBytes()
|
||||||
);
|
)
|
||||||
const thumbprint = base64url.encode(getCertThumbprint(certDer));
|
const thumbprint = base64url.encode(getCertThumbprint(certDer))
|
||||||
|
|
||||||
const helperKey = new NodeRSA();
|
const helperKey = new NodeRSA()
|
||||||
helperKey.importKey(forge.pki.privateKeyToPem(privateKey));
|
helperKey.importKey(forge.pki.privateKeyToPem(privateKey))
|
||||||
const {n: modulus, e: exponent} = helperKey.exportKey('components');
|
const { n: modulus, e: exponent } = helperKey.exportKey('components')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
privateKey: forge.pki.privateKeyToPem(privateKey),
|
privateKey: forge.pki.privateKeyToPem(privateKey),
|
||||||
certDer: certDer,
|
certDer,
|
||||||
thumbPrint: thumbprint,
|
thumbprint: thumbprint.toString(),
|
||||||
exponent: bnToB64(exponent),
|
exponent: bnToB64(exponent),
|
||||||
modulus: modulus.toString('base64')
|
modulus: modulus.toString('base64')
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
module.exports = setup;
|
module.exports = setup
|
||||||
|
|||||||
+1
-1
@@ -14,7 +14,7 @@ spec:
|
|||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- name: auth0mock
|
- name: auth0mock
|
||||||
image: registry.gitlab.com/unboundsoftware/shiny/auth0mock:${COMMIT}
|
image: registry.gitlab.com/unboundsoftware/auth0mock:${COMMIT}
|
||||||
imagePullPolicy: "IfNotPresent"
|
imagePullPolicy: "IfNotPresent"
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
|
|||||||
+11
-5
@@ -6,23 +6,29 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
"dev": "nodemon ./app.js",
|
"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": "",
|
"author": "",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"base64-url": "^2.3.3",
|
"base64-url": "^2.3.3",
|
||||||
"body-parser": "^1.20.0",
|
"body-parser": "^1.20.2",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
"cookie-parser": "^1.4.6",
|
"cookie-parser": "^1.4.6",
|
||||||
"cors": "^2.8.3",
|
"cors": "^2.8.3",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"express": "^4.17.3",
|
"express": "^4.18.2",
|
||||||
"https-localhost": "^4.7.1",
|
"https-localhost": "^4.7.1",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^9.0.0",
|
||||||
"node-forge": "^1.3.1",
|
"node-forge": "^1.3.1",
|
||||||
"node-rsa": "^1.1.1",
|
"node-rsa": "^1.1.1",
|
||||||
"nodemon": "^2.0.15",
|
"nodemon": "^2.0.22",
|
||||||
"serve-favicon": "^2.4.2"
|
"serve-favicon": "^2.4.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"prettier": "^2.8.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user