-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
e7def12
commit 3093677
Showing
15 changed files
with
417 additions
and
7 deletions.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
/** Sets req.data to form data if form-urlencoded, otherwise to query parameters */ | ||
module.exports = function (req, res, next) { | ||
if (req.is('application/x-www-form-urlencoded')) { | ||
req.data = req.body; | ||
} else { | ||
req.data = req.query; | ||
} | ||
next(); | ||
}; |
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,16 @@ | ||
const isSecureRequest = require('../util/isSecureRequest'); | ||
const { getHost } = require('../util/getHost'); | ||
const { logRequest } = require('../logger'); | ||
|
||
/** redirects to HTTPS server if needed */ | ||
module.exports = function redirectToSSL (req, res, next) { | ||
if (isSecureRequest(req) || (process.env.NODE_ENV !== 'production' && !req.app.get('forceSSL'))) { | ||
return next(); | ||
} | ||
|
||
const host = getHost().split(':')[0] + (req.app.get('httpsPort') ? ':' + req.app.get('httpsPort') : ''); | ||
const newUrl = 'https://' + host + req.url; | ||
|
||
res.redirect(302, newUrl); | ||
logRequest(req, '-', 302, 0, '-> ' + newUrl); | ||
}; |
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,12 @@ | ||
const isSecureRequest = require('../util/isSecureRequest'); | ||
const { logRequest } = require('../logger'); | ||
|
||
/** ensures request is secure, if required */ | ||
module.exports = function secureRequest (req, res, next) { | ||
if (isSecureRequest(req) || (process.env.NODE_ENV !== 'production' && !req.app.get('forceSSL'))) { | ||
return next(); | ||
} | ||
|
||
res.status(400).end(); // TODO: add an explanatory message | ||
logRequest(req, '-', 400, 0, 'blocked insecure'); | ||
}; |
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,13 @@ | ||
const core = require('../stores/core'); | ||
const { logRequest } = require('../logger'); | ||
|
||
/** fails request if data.username doesn't pass core.isValidUsername | ||
* TODO: have store validate username | ||
* */ | ||
module.exports = function validUser (req, res, next) { | ||
if (core.isValidUsername(req.data.username)) { return next(); } | ||
|
||
res.status(400).type('text/plain').end(); | ||
|
||
logRequest(req, req.data.username, 400, 0, 'invalid user'); | ||
}; |
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,137 @@ | ||
/* eslint-env node */ | ||
/* eslint-disable camelcase */ | ||
const express = require('express'); | ||
const router = express.Router(); | ||
const formOrQueryData = require('../middleware/formOrQueryData'); | ||
const redirectToSSL = require('../middleware/redirectToSSL'); | ||
const validUser = require('../middleware/validUser'); | ||
const secureRequest = require('../middleware/secureRequest'); | ||
const { logRequest } = require('../logger'); | ||
const qs = require('querystring'); | ||
|
||
const accessStrings = { r: 'Read', rw: 'Read/write' }; | ||
|
||
router.get('/:username', | ||
redirectToSSL, | ||
formOrQueryData, | ||
validUser, | ||
validOAuthRequest, | ||
function (req, res) { | ||
res.render('auth.html', { | ||
title: 'Authorize', | ||
client_host: new URL(req.query.redirect_uri).host, | ||
client_id: req.query.client_id, | ||
redirect_uri: req.query.redirect_uri, | ||
response_type: req.query.response_type, | ||
scope: req.query.scope || '', | ||
state: req.query.state || '', | ||
permissions: parseScope(req.query.scope || ''), | ||
username: req.params.username, | ||
access_strings: accessStrings | ||
}); | ||
logRequest(req, req.params.username, 200); | ||
}); | ||
|
||
router.post('/', | ||
secureRequest, | ||
formOrQueryData, | ||
validUser, | ||
validOAuthRequest, | ||
async function (req, res) { | ||
const locals = req.data; | ||
const username = locals.username.split('@')[0]; | ||
const permissions = parseScope(locals.scope); | ||
|
||
if (locals.deny) { | ||
return error(req, res, 'access_denied', 'The user did not grant permission'); | ||
} | ||
|
||
try { | ||
await req.app.get('streaming store').authenticate({ username, password: locals.password }); | ||
const token = await req.app.get('streaming store').authorize(locals.client_id, username, permissions); | ||
const args = { | ||
access_token: token, | ||
token_type: 'bearer', | ||
...(locals.state && { state: locals.state }) | ||
}; | ||
redirect(req, res, args); | ||
} catch (error) { | ||
locals.title = 'Authorization Failure'; | ||
locals.client_host = new URL(locals.redirect_uri).host; | ||
locals.error = error.message; | ||
locals.permissions = permissions; | ||
locals.access_strings = accessStrings; | ||
locals.state = locals.state || ''; | ||
|
||
res.status(401).render('auth.html', locals); | ||
} | ||
} | ||
); | ||
|
||
function validOAuthRequest (req, res, next) { | ||
if (!req.data.client_id) { | ||
return error(req, res, 'invalid_request', 'Required parameter "client_id" is missing'); | ||
} | ||
if (!req.data.response_type) { | ||
return error(req, res, 'invalid_request', 'Required parameter "response_type" is missing'); | ||
} | ||
if (!req.data.scope) { | ||
return error(req, res, 'invalid_scope', 'Parameter "scope" is invalid'); | ||
} | ||
if (!req.data.redirect_uri) { | ||
return error(req, res, 'invalid_request', 'Required parameter "redirect_uri" is missing'); | ||
} | ||
const uri = new URL(req.data.redirect_uri); | ||
if (!uri.protocol || !uri.hostname) { | ||
return error(req, res, 'invalid_request', 'Parameter "redirect_uri" must be a valid URL'); | ||
} | ||
|
||
if (req.data.response_type !== 'token') { | ||
return error(req, res, 'unsupported_response_type', 'Response type "' + req.data.response_type + '" is not supported'); | ||
} | ||
|
||
next(); | ||
} | ||
|
||
function error (req, res, error, error_description) { | ||
redirect(req, res, { error, error_description }, | ||
`${req.data.username} ${error_description} ${req.data.client_id}`); | ||
} | ||
|
||
function redirect (req, res, args, logNote) { | ||
const hash = qs.stringify(args); | ||
if (req.data.redirect_uri) { | ||
const location = req.data.redirect_uri + '#' + hash; | ||
res.redirect(location); | ||
|
||
if (logNote) { | ||
logRequest(req, req.data.username || '-', 302, 0, logNote, 'warning'); | ||
} else { | ||
logRequest(req, req.data.username || '-', 302, 0, '-> ' + req.data.redirect_uri, 'notice'); | ||
} | ||
} else { | ||
res.status(400).type('text/plain').send(hash); | ||
logRequest(req, req.data.username || '-', 400, hash.length, | ||
logNote || args?.error_description || 'no redirect_uri'); | ||
} | ||
} | ||
|
||
// OAuth.prototype.accessStrings = {r: 'Read', rw: 'Read/write'}; | ||
function parseScope (scope) { | ||
const parts = scope.split(/\s+/); | ||
const scopes = {}; | ||
let pieces; | ||
|
||
for (let i = 0, n = parts.length; i < n; i++) { | ||
pieces = parts[i].split(':'); | ||
pieces[0] = pieces[0].replace(/(.)\/*$/, '$1'); | ||
if (pieces[0] === 'root') pieces[0] = '/'; | ||
|
||
scopes[pieces[0]] = (pieces.length > 1) | ||
? pieces.slice(1).join(':').split('') | ||
: ['r', 'w']; | ||
} | ||
return scopes; | ||
} | ||
|
||
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
/* eslint-env mocha, chai, node */ | ||
|
||
const Armadietto = require('../../lib/armadietto'); | ||
const { shouldImplementOAuth } = require('../oauth.spec'); | ||
|
||
const store = { | ||
authorize (clientId, username, permissions) { | ||
return 'a_token'; | ||
}, | ||
authenticate (params) { | ||
} | ||
}; | ||
|
||
describe('OAuth (monolithic)', function () { | ||
before(function () { | ||
this.store = store; | ||
this.app = new Armadietto({ | ||
bare: true, | ||
store, | ||
http: { }, | ||
logging: { stdout: [], log_dir: './test-log', log_files: ['debug'] } | ||
}); | ||
}); | ||
|
||
shouldImplementOAuth(); | ||
}); |
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,29 @@ | ||
/* eslint-env mocha, chai, node */ | ||
|
||
const { configureLogger } = require('../../lib/logger'); | ||
const { shouldImplementOAuth } = require('../oauth.spec'); | ||
|
||
const mockStore = { | ||
async authorize (_clientId, _username, _permissions) { | ||
return 'a_token'; | ||
}, | ||
async authenticate (params) { | ||
} | ||
}; | ||
|
||
describe('OAuth (modular)', function () { | ||
before(async function () { | ||
configureLogger({ log_dir: './test-log', stdout: [], log_files: ['debug'] }); | ||
|
||
this.store = mockStore; | ||
|
||
this.app = require('../../lib/app'); | ||
this.app.set('streaming store', this.store); | ||
this.app.locals.title = 'Test Armadietto'; | ||
this.app.locals.basePath = ''; | ||
this.app.locals.host = 'localhost:xxxx'; | ||
this.app.locals.signup = false; | ||
}); | ||
|
||
shouldImplementOAuth(); | ||
}); |
Oops, something went wrong.