Merge branch 'issuer-rewrite'

# Conflicts:
#	README.md
#	index.html
#	install.sh
This commit is contained in:
tigerbot 2017-10-19 16:49:03 -06:00
commit d28ab68abc
4 changed files with 79 additions and 147 deletions

View File

@ -1,10 +1,9 @@
This is a WALNUT module representing the html package for oauth3.org. This is a WALNUT module representing the html package for the oauth3.org popup.
It must be installed to `/srv/walnut/packages/pages/issuer@oauth3.org` It must be installed to `/srv/walnut/packages/pages/issuer@oauth3.org`
```bash ```bash
git clone git@git.daplie.com:OAuth3/issuer_oauth3.org.git /srv/walnut/packages/pages/issuer@oauth3.org git clone git@git.daplie.com:OAuth3/org.oauth3.git /srv/walnut/packages/pages/issuer@oauth3.org
ln -s issuer@oauth3.org /srv/walnut/packages/pages/org.oauth3
pushd /srv/walnut/packages/pages/issuer@oauth3.org pushd /srv/walnut/packages/pages/issuer@oauth3.org
bash ./install.sh bash ./install.sh
popd popd
@ -12,9 +11,7 @@ popd
```bash ```bash
echo "issuer@oauth3.org" >> /srv/walnut/packages/sites/EXAMPLE.COM echo "issuer@oauth3.org" >> /srv/walnut/packages/sites/EXAMPLE.COM
echo "org.oauth3" >> /srv/walnut/packages/sites/EXAMPLE.COM
``` ```
This uses the OAuth3 JavaScript SDK `oauth3.js` as a subpackage in `/srv/walnut/packages/pages/issuer@oauth3.org/assets/oauth3.org`. This uses the OAuth3 JavaScript SDK `oauth3.js` as a subpackage in
`/srv/walnut/packages/pages/issuer@oauth3.org/assets/oauth3.org`.
The 'login popup' is hosted on our tardigrade proxy VM.

View File

@ -71,8 +71,7 @@
<input class="js-remember-checkbox hidden" type="checkbox"></input> <input class="js-remember-checkbox hidden" type="checkbox"></input>
</label> </label>
</div> </div>
<!-- <button class="dap-full-button-green js-remember-btn js-authz-remember-me">SIGN IN ONCE</button> --> <button type="submit" class="btn btn-primary submit-btn dap-full-button-green js-submit-code-btn" disabled>Submit</button>
<button type="submit" class="btn btn-primary submit-btn dap-full-button-green js-remember-btn js-authz-remember-me" disabled>Submit</button>
<!-- <a href="./authnocode.html" target="_blank" class="btn btn-primary">Send Code Again</a> --> <!-- <a href="./authnocode.html" target="_blank" class="btn btn-primary">Send Code Again</a> -->
<button class="btn btn-primary js-edit-email-button" type="button">Edit My Email</button> <button class="btn btn-primary js-edit-email-button" type="button">Edit My Email</button>
</form> </form>
@ -146,11 +145,11 @@
<script src="./js/jquery-2.2.0.min.js"></script> <script src="./js/jquery-2.2.0.min.js"></script>
<script src="./js/jquery.mask.min.js"></script> <script src="./js/jquery.mask.min.js"></script>
<script src="./js/bootstrap.min.js"></script> <script src="./js/bootstrap.min.js"></script>
<script src="/assets/oauth3.org/oauth3.core.js"></script>
<script src="/assets/oauth3.org/oauth3.crypto.js"></script>
<script src="/assets/oauth3.org/oauth3.issuer.js"></script>
<script src="./js/issuer.js"></script> <script src="./js/issuer.js"></script>
<script src="./js/script.js"></script> <script src="./js/script.js"></script>
<script src="/assets/oauth3.org/oauth3.core.js"></script>
<script src="/assets/oauth3.org/oauth3.issuer.js"></script>
<!--script src="/assets/oauth3.org/oauth3.mock.js"></script-->
</body> </body>
</html> </html>

View File

@ -2,7 +2,7 @@
set -e set -e
set -u set -u
# git clone https://git.daplie.com/OAuth3/issuer_oauth3.org.git /srv/walnut/packages/pages/issuer@oauth3.org # git clone https://git.daplie.com/OAuth3/org.oauth3.git /srv/walnut/packages/pages/azp@oauth3.org
mkdir -p assets mkdir -p assets
if ! [ -d ./assets/oauth3.org ]; then if ! [ -d ./assets/oauth3.org ]; then

View File

@ -9,7 +9,7 @@ $(function () {
var OAUTH3 = window.OAUTH3; var OAUTH3 = window.OAUTH3;
var CONFIG = { var CONFIG = {
host: OAUTH3.utils.clientUri(window.location) host: OAUTH3.clientUri(window.location)
, directives: null // will be populated before the login button appears , directives: null // will be populated before the login button appears
}; };
var loc = window.location; var loc = window.location;
@ -20,22 +20,8 @@ $(function () {
}; };
$('.js-scopes-container').html(''); $('.js-scopes-container').html('');
/*
OAUTH3._hooks.sessions.all = function (providerUri) {
};
*/
OAUTH3._hooks = { sessions: {} };
OAUTH3._hooks.sessions.get = function (providerUri, id) {
return JSON.parse(window.localStorage.getItem('session-' + providerUri + (id || '')) || 'null');
};
OAUTH3._hooks.sessions.set = function (providerUri, newSession, id) {
window.localStorage.setItem('session-' + providerUri, JSON.stringify(newSession));
window.localStorage.setItem('session-' + providerUri + (id || newSession.id || newSession.token.id || ''), JSON.stringify(newSession));
return newSession;
};
// TODO let query.parse do location.hash || location.search || location // TODO let query.parse do location.hash || location.search || location
var clientParams = OAUTH3.query.parse(window.location.hash || window.location.search); var clientParams = OAUTH3.query.parse(loc.hash || loc.search);
if (/authorization_dialog/.test(window.location.href)) { if (/authorization_dialog/.test(window.location.href)) {
// OAUTH3.lintClientParams(params, window) // OAUTH3.lintClientParams(params, window)
// OAUTH3.normalizeClientParams(params, window) // OAUTH3.normalizeClientParams(params, window)
@ -58,12 +44,12 @@ $(function () {
+ "'" + OAUTH3.url.normalize(window.document.referrer) + "'" + "'" + OAUTH3.url.normalize(window.document.referrer) + "'"
); );
} }
if (clientParams.client_uri) { if (clientParams.client_uri && clientParams.client_uri !== clientParams.client_id) {
console.warn("'client_id' should be used instead of 'client_uri'"); console.warn("'client_id' should be used instead of 'client_uri'");
} }
if (!(clientParams.client_id || clientParams.client_uri)) { if (!(clientParams.client_id || clientParams.client_uri)) {
window.alert("'response_type' must exist and be either 'token' (implicit flow) or 'code' (authorization flow)"); window.alert("'client_id' must exist as the uri identifying the client");
console.error("'response_type' must exist and be either 'token' (implicit flow) or 'code' (authorization flow)"); console.error("'client_id' must exist as the uri identifying the client");
clientParams.client_id = clientParams.client_uri = OAUTH3.url.normalize(window.document.referrer); clientParams.client_id = clientParams.client_uri = OAUTH3.url.normalize(window.document.referrer);
} }
if (!clientParams.redirect_uri) { if (!clientParams.redirect_uri) {
@ -88,22 +74,21 @@ $(function () {
function getSession(providerUri) { function getSession(providerUri) {
return OAUTH3.hooks.session.get(providerUri).then(function (session) { return OAUTH3.hooks.session.get(providerUri).then(function (session) {
if (session && session.access_token) { if (session && session.access_token) {
normalizeSession(session); normalizeSession(session);
return OAUTH3.PromiseA.resolve(session); return OAUTH3.PromiseA.resolve(session);
} }
else { else {
return OAUTH3.PromiseA.reject(new Error("no access_token in session")); return OAUTH3.PromiseA.reject(new Error("no access_token in session"));
} }
}); });
} }
function getGrants(session) { function getGrants(session) {
var clientObj = OAUTH3.query.parse(loc.hash || loc.search); var clientLogo = OAUTH3.url.normalize(clientParams.client_uri) // optional relative logo ?
var clientLogo = OAUTH3.url.normalize(clientObj.client_uri) // optional relative logo ?
+ '/.well-known/oauth3/logo-128x128.png' + '/.well-known/oauth3/logo-128x128.png'
; ;
var callbackUrl;
// TODO put in directives.json or similar // TODO put in directives.json or similar
var grantDescriptions = { var grantDescriptions = {
// deprecated // deprecated
@ -139,10 +124,10 @@ $(function () {
$('.js-client-logo').attr('src', clientLogo); $('.js-client-logo').attr('src', clientLogo);
//$('.js-user-avatar').attr('src', userAvatar); //$('.js-user-avatar').attr('src', userAvatar);
return OAUTH3.authz.scopes(CONFIG.host, session, clientObj).then(function (scopes) { return OAUTH3.authz.scopes(CONFIG.host, session, clientParams).then(function (scopes) {
if (!scopes.pending.length) { if (!scopes.pending.length) {
// looks like we've done all of this before // looks like we've done all of this before
OAUTH3.authz.redirectWithToken(CONFIG.host, session, clientObj, scopes); OAUTH3.authz.redirectWithToken(CONFIG.host, session, clientParams, scopes);
return; return;
} }
@ -150,10 +135,12 @@ $(function () {
// TODO secure iFrame from click-jacking by requiring input? // TODO secure iFrame from click-jacking by requiring input?
// ex: input.security-code[type="text"].val(Math.random()); input.js-verify-code[placeholder="Type what you see"] // ex: input.security-code[type="text"].val(Math.random()); input.js-verify-code[placeholder="Type what you see"]
if (OAUTH3._browser.isIframe()) { if (OAUTH3._browser.isIframe()) {
callbackUrl = clientObj.redirect_uri + '#state=' + clientObj.state + '&error=access_denied&error_description=' location.href = clientParams.redirect_uri +'#'+ OAUTH3.query.stringify({
+ encodeURIComponent("You're requesting permission in an iframe, but the permissions have not yet been granted") state: clientParams.state
+ '&error_uri=' + encodeURIComponent('https://oauth3.org/docs/errors/#E_IFRAME_DENIED'); , error: 'access_denied'
location.href = callbackUrl; , error_description: encodeURIComponent("You're requesting permission in an iframe, but the permissions have not yet been granted")
, error_uri: encodeURIComponent('https://oauth3.org/docs/errors/#E_IFRAME_DENIED')
});
return; return;
} }
@ -186,9 +173,6 @@ $(function () {
}); });
$('.js-authz').show().addClass('in'); $('.js-authz').show().addClass('in');
}, function (err) {
window.alert('grantResults: ' + err.message);
console.error('scope results', err);
}); });
} }
@ -224,56 +208,27 @@ $(function () {
// TODO loading // TODO loading
email = $('.js-oauth3-email').val(); email = $('.js-oauth3-email').val();
return OAUTH3.authn.loginMeta(CONFIG.directives, {email: email, mock: true}).then(function (userResults) { return OAUTH3.authn.otp(CONFIG.directives, {email: email, mock: true}).then(function (otpResults) {
if (!userResults.data.error) {
console.log('User exists:', userResults); if (otpResults.data.error) {
window.alert('otpResults: ' + otpResults.data.error_description || otpResults.data.error.message);
return;
} }
if (userResults.data.error) { var ua = window.navigator.userAgent;
$('.js-authn-show').removeAttr('disabled'); $('.js-sniffed-device').text(ua);
console.warn('User does not exist:', email); $('.js-userid-container').removeClass('in').hide();
console.warn('User Results:', userResults); $('.js-authn').show().addClass('in');
//window.alert('userResults: ' + userResults.data.error_description || userResults.data.error.message); $('.js-authn-otp-uuid').val(otpResults.data.uuid);
//return;
}
return OAUTH3.authn.otp(CONFIG.directives, {email: email, mock: true}).then(function (otpResults) { $('.js-user-email').text(email);
if (otpResults.data.error) {
window.alert('otpResults: ' + otpResults.data.error_description || otpResults.data.error.message);
return;
}
var ua = window.navigator.userAgent;
$('.js-sniffed-device').text(ua);
$('.js-userid-container').removeClass('in').hide();
$('.js-authn').show().addClass('in');
$('.js-authn-otp-uuid').val(otpResults.data.uuid);
$('.js-user-email').text(email);
});
});
};
util.rememberDevice = function (ev) {
ev.preventDefault();
ev.stopPropagation();
util.submitLoginCode({
rememberDevice: true
});
};
util.rememberDeviceNot = function (ev) {
ev.preventDefault();
ev.stopPropagation();
util.submitLoginCode({
rememberDevice: false
}); });
}; };
// Reference Implementation // Reference Implementation
util.submitLoginCode = function (ev) {
util.submitLoginCode = function (opts) { ev.preventDefault();
ev.stopPropagation();
// TODO // TODO
// perhaps we should check that the code is valid before continuing to login (so that we don't send the key) // perhaps we should check that the code is valid before continuing to login (so that we don't send the key)
@ -281,7 +236,6 @@ $(function () {
// TODO // TODO
// we should be sending the public key for this device as a jwk along with the authentication // we should be sending the public key for this device as a jwk along with the authentication
// (and how long to remember this device) // (and how long to remember this device)
var uuid = $('.js-authn-otp-uuid').val(); var uuid = $('.js-authn-otp-uuid').val();
var code = $('.js-authn-otp-code').val().trim(); var code = $('.js-authn-otp-code').val().trim();
return OAUTH3.authn.resourceOwnerPassword(CONFIG.directives, { return OAUTH3.authn.resourceOwnerPassword(CONFIG.directives, {
@ -295,38 +249,35 @@ $(function () {
// TODO should be otp_id (agnostic of uuid) // TODO should be otp_id (agnostic of uuid)
, otp_uuid: uuid , otp_uuid: uuid
// add expiration to the refresh token and/or public key // add expiration to the refresh token and/or public key
, expire: opts.rememberDevice || (1 * 60 * 60 * 1000) , remember_device: $('.js-remember-label').find('.js-remember-checkbox').prop('checked')
, mock: true , mock: true
}).then(function (session) { }).then(function (session) {
$('.js-authn').removeClass('in').hide(); $('.js-authn').removeClass('in').hide();
if (session.token.sub) {
function getAccount(session) { return OAUTH3.PromiseA.resolve(session);
if (session.token.sub) {
return OAUTH3.PromiseA.resolve(session);
}
return OAUTH3.requests.accounts.create(CONFIG.directives, session, {
display_name: email.replace(/@.*/, '')
, comment: "created for '" + email + "' by '" + CONFIG.host + "'"
, priority: 1000 // default priority for first account
, name: undefined // TODO we could ask in the UI
}).then(function (resp) {
var results = resp.data;
return OAUTH3.hooks.session.refresh(session, {
access_token: (results.access_token || results.accessToken)
, refresh_token: (results.refresh_token || results.refreshToken)
});
});
} }
return getAccount(session).then(function () { return OAUTH3.requests.accounts.create(CONFIG.directives, session, {
return getGrants(session); display_name: email.replace(/@.*/, '')
, comment: "created for '" + email + "' by '" + CONFIG.host + "'"
, priority: 1000 // default priority for first account
, name: undefined // TODO we could ask in the UI
}).then(function (resp) {
var results = resp.data;
return OAUTH3.hooks.session.refresh(session, {
access_token: (results.access_token || results.accessToken)
, refresh_token: (results.refresh_token || results.refreshToken)
});
});
}).then(function (session) {
return getGrants(session).catch(function (err) {
window.alert('grantResults: ' + err.message);
console.error('scope results', err);
}); });
}, function (error) { }, function (error) {
console.error(error);
$('.error-msg').text('Incorrect code'); $('.error-msg').text('Incorrect code');
}); });
}; };
util.acceptScopesAndLogin = function (ev) { util.acceptScopesAndLogin = function (ev) {
ev.preventDefault(); ev.preventDefault();
@ -343,8 +294,6 @@ $(function () {
}); });
getSession(CONFIG.host).then(function (session) { getSession(CONFIG.host).then(function (session) {
var clientParams = OAUTH3.query.parse(loc.hash || loc.search);
return OAUTH3.authz.scopes(CONFIG.host, session, clientParams).then(function (scopes) { return OAUTH3.authz.scopes(CONFIG.host, session, clientParams).then(function (scopes) {
scopes.new = acceptedScopes; scopes.new = acceptedScopes;
return OAUTH3.authz.redirectWithToken(CONFIG.host, session, clientParams, scopes); return OAUTH3.authz.redirectWithToken(CONFIG.host, session, clientParams, scopes);
@ -358,21 +307,17 @@ $(function () {
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
var loginWinObj = OAUTH3.query.parse(loc.hash || loc.search);
var denyObj = { var denyObj = {
error: 'access_denied' error: 'access_denied'
, error_description: 'The user has denied access.' , error_description: 'The user has denied access.'
, error_uri: 'https://' + CONFIG.host + '/.well-known/oauth3/errors.html#/?error=access_denied' , error_uri: 'https://' + CONFIG.host + '/.well-known/oauth3/errors.html#/?error=access_denied'
, state: loginWinObj.state , state: clientParams.state
, scope: loginWinObj.scope , scope: clientParams.scope
}; };
window.location = loginWinObj.redirect_uri + '#' + OAUTH3.query.stringify(denyObj); window.location = clientParams.redirect_uri + '#' + OAUTH3.query.stringify(denyObj);
}; };
util.handleLogout = function () { util.handleLogout = function () {
var clientParams = OAUTH3.query.parse(loc.hash || loc.search);
localStorage.clear(); localStorage.clear();
clientParams.redirect_uri += '?' + OAUTH3.query.stringify({ clientParams.redirect_uri += '?' + OAUTH3.query.stringify({
@ -385,21 +330,21 @@ $(function () {
util.editEmail = function () { util.editEmail = function () {
$('.js-authn').hide(); $('.js-authn').hide();
$('.js-userid-container').show(); $('.js-userid-container').show();
debugger;
}; };
// //
// Page Setup // Page Setup
// //
$('.js-authorization-dialog').hide();
$('.js-logout-container').hide();
$('.js-userid-container').hide(); $('.js-userid-container').hide();
$('.js-authn').hide(); $('.js-authn').hide();
$('.js-authz').hide(); $('.js-authz').hide();
$('body').on('click', '.js-logout', util.handleLogout); $('body').on('click', '.js-logout', util.handleLogout);
$('body').on('click', '.js-authn-show', util.submitAuthEmail); $('body').on('click', '.js-authn-show', util.submitAuthEmail);
$('body').on('click', '.js-authz-remember-me', util.rememberDevice); $('body').on('click', '.js-submit-code-btn', util.submitLoginCode);
$('body').on('click', '.js-authz-remember-me-not', util.rememberDeviceNot);
$('body').on('click', '.js-login-allow', util.acceptScopesAndLogin); $('body').on('click', '.js-login-allow', util.acceptScopesAndLogin);
$('body').on('click', '.js-login-deny', util.closeLoginDeny); $('body').on('click', '.js-login-deny', util.closeLoginDeny);
$('body').on('click', '.js-edit-email-button', util.editEmail); $('body').on('click', '.js-edit-email-button', util.editEmail);
@ -408,40 +353,33 @@ $(function () {
function handleAuthorizationDialog() { function handleAuthorizationDialog() {
return getSession(CONFIG.host).then(function (session) { return getSession(CONFIG.host).then(function (session) {
return getGrants(session); return getGrants(session);
}, function (e) { }).catch(function () {
var clientObj = OAUTH3.query.parse(loc.hash || loc.search);
// TODO select the providers the client wants to show // TODO select the providers the client wants to show
// providers=daplie.com,facebook.com,google.com // etc // providers=daplie.com,facebook.com,google.com // etc
// TODO let the client specify switch_user // TODO let the client specify switch_user
// TODO let the client specify relogin if stale // TODO let the client specify relogin if stale
if (OAUTH3._browser.isIframe()) { if (OAUTH3._browser.isIframe()) {
var callbackUrl = clientObj.redirect_uri + '#state=' + clientObj.state + '&error=access_denied&error_description=' location.href = clientParams.redirect_uri +'#'+ OAUTH3.query.stringify({
+ encodeURIComponent("You're requesting permission in an iframe, but the user is not yet authenticated") state: clientParams.state
+ '&error_uri=' + encodeURIComponent('https://oauth3.org/docs/errors/#E_IFRAME_DENIED'); , error: 'access_denied'
location.href = callbackUrl; , error_description: encodeURIComponent("You're requesting permission in an iframe, but the user is not yet authenticated")
, error_uri: encodeURIComponent('https://oauth3.org/docs/errors/#E_IFRAME_DENIED')
});
} }
if (clientParams.subject) { if (clientParams.subject) {
$('.js-oauth3-email').val(clientParams.subject); $('.js-oauth3-email').val(clientParams.subject);
$('.js-authn-show').prop('disabled', false); $('.js-authn-show').prop('disabled', false);
} }
$('.js-userid-container').show(); $('.js-userid-container').show();
}).then(function () {
//$('body').addClass('in');
}); });
} }
// Session initialization // Session initialization
return OAUTH3.discover( return OAUTH3.discover(CONFIG.host, { client_uri: CONFIG.host }).then(function (directives) {
OAUTH3.clientUri(window.location)
, { client_uri: OAUTH3.clientUri(window.location) }
).then(function (directives) {
// TODO cache directives in memory (and storage) // TODO cache directives in memory (and storage)
CONFIG.directives = directives; CONFIG.directives = directives;
directives.issuer = directives.issuer || (window.location.host + window.location.pathname).replace(/\/$/, ''); directives.issuer = directives.issuer || (window.location.host + window.location.pathname).replace(/\/$/, '');
$('.js-authorization-dialog').hide();
$('.js-logout-container').hide();
if (/authorization_dialog/.test(window.location.href)) { if (/authorization_dialog/.test(window.location.href)) {
$('.js-authorization-dialog').show(); $('.js-authorization-dialog').show();
handleAuthorizationDialog(); handleAuthorizationDialog();
@ -451,7 +389,5 @@ $(function () {
} }
$('body').addClass('in'); $('body').addClass('in');
}); });
}); });