-
Notifications
You must be signed in to change notification settings - Fork 2.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
FEAT: Open ID Connect authentication #4010
Open
oechsler
wants to merge
30
commits into
NginxProxyManager:develop
Choose a base branch
from
oechsler:FEAT/open-id-connect-authentication
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+1,527
−1,310
Open
Changes from 19 commits
Commits
Show all changes
30 commits
Select commit
Hold shift + click to select a range
caeb293
FEAT: Add Open ID Connect authentication method
marekful 3e2a411
chore: add oidc setting db entry during setup
marekful 457d1a7
chore: improve oidc setting ui
marekful 8350271
chore: add message texts
marekful bc0b466
refactor: improve code structure
marekful baee464
chore: improve error handling
marekful 6f98fa6
refactor: satisfy linter requirements
marekful df5ab36
chore: update comments, remove debug logging
marekful ef64edd
fix: add database migration for oidc-config setting
marekful fd49644
fix: linter
marekful d0d36a9
fix: add oidc-config setting via setup.js rather than migrations
marekful 6ed6415
fix: add oidc logger and replace console logging
marekful 0f588ba
fix: indentation
marekful 0b09f03
Merge remote-tracking branch 'origin/develop' into FEAT/open-id-conne…
oechsler 8b84117
Fix configuration template
oechsler 7196dfa
Merge remote-tracking branch 'origin/develop' into FEAT/open-id-conne…
oechsler 0b126ca
Add oidc-config to OpenAPI schema
oechsler 7ef52d8
Update yarn.lock
oechsler 1a030a6
Enforce token auth for odic config PUT call
oechsler eb312cc
Remove nodemon dependency in package.json
oechsler 637b773
Make the error message for when a user does not exist when attempting…
chutch1122 e4b87d0
Add documentation for configuring SSO with OIDC
chutch1122 81aa8a4
Make 'Redirect URL' match the name of the field.
chutch1122 1ed15b3
Add Cypress tests for updating the OIDC configuration
chutch1122 529c84f
Add UI E2E tests for the login page for OIDC being enabled and when i…
chutch1122 6e41d7b
Update warning in documentation to be consistent with the rest of the…
chutch1122 2cae60d
Merge pull request #1 from chutch1122/FEAT/open-id-connect-authentica…
oechsler d714fee
Merge remote-tracking branch 'upstream/develop' into FEAT/open-id-con…
chutch1122 46f0b52
Update error messages for login tests
chutch1122 03fbebc
Merge pull request #2 from chutch1122/FEAT/open-id-connect-authentica…
oechsler File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
const crypto = require('crypto'); | ||
const error = require('../lib/error'); | ||
const express = require('express'); | ||
const jwtdecode = require('../lib/express/jwt-decode'); | ||
const logger = require('../logger').oidc; | ||
const oidc = require('openid-client'); | ||
const settingModel = require('../models/setting'); | ||
const internalToken = require('../internal/token'); | ||
|
||
let router = express.Router({ | ||
caseSensitive: true, | ||
strict: true, | ||
mergeParams: true | ||
}); | ||
|
||
router | ||
.route('/') | ||
.options((req, res) => { | ||
res.sendStatus(204); | ||
}) | ||
.all(jwtdecode()) | ||
|
||
/** | ||
* GET /api/oidc | ||
* | ||
* OAuth Authorization Code flow initialisation | ||
*/ | ||
.get(jwtdecode(), async (req, res) => { | ||
logger.info('Initializing OAuth flow'); | ||
settingModel | ||
.query() | ||
.where({id: 'oidc-config'}) | ||
.first() | ||
.then((row) => getInitParams(req, row)) | ||
.then((params) => redirectToAuthorizationURL(res, params)) | ||
.catch((err) => redirectWithError(res, err)); | ||
}); | ||
|
||
|
||
router | ||
.route('/callback') | ||
.options((req, res) => { | ||
res.sendStatus(204); | ||
}) | ||
.all(jwtdecode()) | ||
|
||
/** | ||
* GET /api/oidc/callback | ||
* | ||
* Oauth Authorization Code flow callback | ||
*/ | ||
.get(jwtdecode(), async (req, res) => { | ||
logger.info('Processing callback'); | ||
settingModel | ||
.query() | ||
.where({id: 'oidc-config'}) | ||
.first() | ||
.then((settings) => validateCallback(req, settings)) | ||
.then((token) => redirectWithJwtToken(res, token)) | ||
.catch((err) => redirectWithError(res, err)); | ||
}); | ||
|
||
/** | ||
* Executes discovery and returns the configured `openid-client` client | ||
* | ||
* @param {Setting} row | ||
* */ | ||
let getClient = async (row) => { | ||
let issuer; | ||
try { | ||
issuer = await oidc.Issuer.discover(row.meta.issuerURL); | ||
} catch (err) { | ||
throw new error.AuthError(`Discovery failed for the specified URL with message: ${err.message}`); | ||
} | ||
|
||
return new issuer.Client({ | ||
client_id: row.meta.clientID, | ||
client_secret: row.meta.clientSecret, | ||
redirect_uris: [row.meta.redirectURL], | ||
response_types: ['code'], | ||
}); | ||
}; | ||
|
||
/** | ||
* Generates state, nonce and authorization url. | ||
* | ||
* @param {Request} req | ||
* @param {Setting} row | ||
* @return { {String}, {String}, {String} } state, nonce and url | ||
* */ | ||
let getInitParams = async (req, row) => { | ||
let client = await getClient(row), | ||
state = crypto.randomUUID(), | ||
nonce = crypto.randomUUID(), | ||
url = client.authorizationUrl({ | ||
scope: 'openid email profile', | ||
resource: `${req.protocol}://${req.get('host')}${req.originalUrl}`, | ||
state, | ||
nonce, | ||
}); | ||
|
||
return { state, nonce, url }; | ||
}; | ||
|
||
/** | ||
* Parses state and nonce from cookie during the callback phase. | ||
* | ||
* @param {Request} req | ||
* @return { {String}, {String} } state and nonce | ||
* */ | ||
let parseStateFromCookie = (req) => { | ||
let state, nonce; | ||
let cookies = req.headers.cookie.split(';'); | ||
for (let cookie of cookies) { | ||
if (cookie.split('=')[0].trim() === 'npm_oidc') { | ||
let raw = cookie.split('=')[1], | ||
val = raw.split('--'); | ||
state = val[0].trim(); | ||
nonce = val[1].trim(); | ||
break; | ||
} | ||
} | ||
|
||
return { state, nonce }; | ||
}; | ||
|
||
/** | ||
* Executes validation of callback parameters. | ||
* | ||
* @param {Request} req | ||
* @param {Setting} settings | ||
* @return {Promise} a promise resolving to a jwt token | ||
* */ | ||
let validateCallback = async (req, settings) => { | ||
let client = await getClient(settings); | ||
let { state, nonce } = parseStateFromCookie(req); | ||
|
||
const params = client.callbackParams(req); | ||
const tokenSet = await client.callback(settings.meta.redirectURL, params, { state, nonce }); | ||
let claims = tokenSet.claims(); | ||
|
||
if (!claims.email) { | ||
throw new error.AuthError('The Identity Provider didn\'t send the \'email\' claim'); | ||
} else { | ||
logger.info('Successful authentication for email ' + claims.email); | ||
} | ||
|
||
return internalToken.getTokenFromOAuthClaim({ identity: claims.email }); | ||
}; | ||
|
||
let redirectToAuthorizationURL = (res, params) => { | ||
logger.info('Authorization URL: ' + params.url); | ||
res.cookie('npm_oidc', params.state + '--' + params.nonce); | ||
res.redirect(params.url); | ||
}; | ||
|
||
let redirectWithJwtToken = (res, token) => { | ||
res.cookie('npm_oidc', token.token + '---' + token.expires); | ||
res.redirect('/login'); | ||
}; | ||
|
||
let redirectWithError = (res, error) => { | ||
logger.error('Callback error: ' + error.message); | ||
res.cookie('npm_oidc_error', error.message); | ||
res.redirect('/login'); | ||
}; | ||
|
||
module.exports = router; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nodemon
is already indevDependencies
- adding it here is unnecessary and will add bloat to the final image