mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-03 23:00:14 -05:00
Added oauth login and invitation acceptance
issue https://github.com/TryGhost/Team/issues/614 - Users who have a password can directly sign-in via oauth - User who are logged-in get their password disabled - Users accepting an invitation get their password disabled - The way we disable password is by setting it to a long random password
This commit is contained in:
parent
c02b0a19ac
commit
c471ae11d4
6 changed files with 202 additions and 2 deletions
|
@ -47,3 +47,5 @@ module.exports.createSessionFromToken = sessionFromToken({
|
|||
getLookupFromToken: ssoAdapter.getIdentityFromCredentials.bind(ssoAdapter),
|
||||
getTokenFromRequest: ssoAdapter.getRequestCredentials.bind(ssoAdapter)
|
||||
});
|
||||
|
||||
module.exports.sessionService = sessionService;
|
||||
|
|
124
core/server/web/oauth/app.js
Normal file
124
core/server/web/oauth/app.js
Normal file
|
@ -0,0 +1,124 @@
|
|||
const debug = require('ghost-ignition').debug('web:oauth:app');
|
||||
const {URL} = require('url');
|
||||
const passport = require('passport');
|
||||
const GoogleStrategy = require('passport-google-oauth20').Strategy;
|
||||
const express = require('../../../shared/express');
|
||||
const urlUtils = require('../../../shared/url-utils');
|
||||
const shared = require('../shared');
|
||||
const config = require('../../../shared/config');
|
||||
const settingsCache = require('../../services/settings/cache');
|
||||
const models = require('../../models');
|
||||
const auth = require('../../services/auth');
|
||||
|
||||
function randomPassword() {
|
||||
return require('crypto').randomBytes(128).toString('hex');
|
||||
}
|
||||
|
||||
module.exports = function setupOAuthApp() {
|
||||
debug('OAuth App setup start');
|
||||
const oauthApp = express('oauth');
|
||||
if (!config.get('enableDeveloperExperiments')) {
|
||||
debug('OAuth App setup skipped');
|
||||
return oauthApp;
|
||||
}
|
||||
|
||||
// send 503 json response in case of maintenance
|
||||
oauthApp.use(shared.middlewares.maintenance);
|
||||
|
||||
function googleOAuthMiddleware(clientId, secret) {
|
||||
return (req, res, next) => {
|
||||
const callbackUrl = new URL(urlUtils.getSiteUrl());
|
||||
callbackUrl.pathname = '/ghost/oauth/google/callback';
|
||||
passport.authenticate(new GoogleStrategy({
|
||||
clientID: clientId,
|
||||
clientSecret: secret,
|
||||
callbackURL: callbackUrl.href
|
||||
}, async function (accessToken, refreshToken, profile, cb) {
|
||||
if (req.user) {
|
||||
const emails = profile.emails.filter(email => email.verified === true).map(email => email.value);
|
||||
|
||||
if (!emails.includes(req.user.get('email'))) {
|
||||
return res.redirect('/ghost/#/staff/?message=oauth-linking-failed');
|
||||
}
|
||||
|
||||
//Associate logged-in user with oauth account
|
||||
req.user.set('password', randomPassword());
|
||||
await req.user.save();
|
||||
} else {
|
||||
//Find user in DB and log-in
|
||||
const emails = profile.emails.filter(email => email.verified === true);
|
||||
if (emails.length < 1) {
|
||||
return res.redirect('/ghost/#/signin?message=login-failed');
|
||||
}
|
||||
const email = emails[0].value;
|
||||
|
||||
let user = await models.User.findOne({
|
||||
email: email
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
const options = {context: {internal: true}};
|
||||
let invite = await models.Invite.findOne({email, status: 'sent'}, options);
|
||||
|
||||
if (!invite || invite.get('expires') < Date.now()) {
|
||||
return res.redirect('/ghost/#/signin?message=login-failed');
|
||||
}
|
||||
|
||||
//Accept invite
|
||||
user = await models.User.add({
|
||||
email: email,
|
||||
name: profile.displayName,
|
||||
password: randomPassword(),
|
||||
roles: [invite.toJSON().role_id]
|
||||
}, options);
|
||||
|
||||
await invite.destroy(options);
|
||||
}
|
||||
|
||||
req.user = user;
|
||||
}
|
||||
|
||||
await auth.session.sessionService.createSessionForUser(req, res, req.user);
|
||||
|
||||
return res.redirect('/ghost/');
|
||||
}), {
|
||||
scope: ['profile', 'email'],
|
||||
session: false
|
||||
})(req, res, next);
|
||||
};
|
||||
}
|
||||
|
||||
oauthApp.get('/:provider', auth.authenticate.authenticateAdminApi, (req, res, next) => {
|
||||
if (req.params.provider !== 'google') {
|
||||
return res.sendStatus(404);
|
||||
}
|
||||
|
||||
const clientId = settingsCache.get('oauth_client_id');
|
||||
const secret = settingsCache.get('oauth_client_secret');
|
||||
|
||||
if (clientId && secret) {
|
||||
return googleOAuthMiddleware(clientId, secret)(req, res, next);
|
||||
}
|
||||
|
||||
res.sendStatus(404);
|
||||
});
|
||||
|
||||
oauthApp.get('/:provider/callback', auth.authenticate.authenticateAdminApi, (req, res, next) => {
|
||||
if (req.params.provider !== 'google') {
|
||||
return res.sendStatus(404);
|
||||
}
|
||||
|
||||
const clientId = settingsCache.get('oauth_client_id');
|
||||
const secret = settingsCache.get('oauth_client_secret');
|
||||
|
||||
if (clientId && secret) {
|
||||
return googleOAuthMiddleware(clientId, secret)(req, res, next);
|
||||
}
|
||||
|
||||
res.sendStatus(404);
|
||||
});
|
||||
|
||||
debug('OAuth App setup end');
|
||||
|
||||
return oauthApp;
|
||||
};
|
1
core/server/web/oauth/index.js
Normal file
1
core/server/web/oauth/index.js
Normal file
|
@ -0,0 +1 @@
|
|||
module.exports = require('./app');
|
|
@ -46,6 +46,7 @@ module.exports = function setupParentApp(options = {}) {
|
|||
// Wrap the admin and API apps into a single express app for use with vhost
|
||||
const backendApp = express('backend');
|
||||
backendApp.use('/ghost/api', require('../api')());
|
||||
backendApp.use('/ghost/oauth', require('../oauth')());
|
||||
backendApp.use('/ghost/.well-known', require('../well-known')());
|
||||
backendApp.use('/ghost', require('../../services/auth/session').createSessionFromToken, require('../admin')());
|
||||
|
||||
|
|
|
@ -128,6 +128,8 @@
|
|||
"node-jose": "2.0.0",
|
||||
"nodemailer": "0.7.1",
|
||||
"oembed-parser": "1.3.7",
|
||||
"passport": "^0.4.1",
|
||||
"passport-google-oauth": "^2.0.0",
|
||||
"path-match": "1.2.4",
|
||||
"probe-image-size": "5.0.0",
|
||||
"rss": "1.2.2",
|
||||
|
|
74
yarn.lock
74
yarn.lock
|
@ -1323,7 +1323,7 @@ base64-js@^1.0.2:
|
|||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1"
|
||||
integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==
|
||||
|
||||
base64url@^3.0.1:
|
||||
base64url@3.x.x, base64url@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d"
|
||||
integrity sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==
|
||||
|
@ -6962,6 +6962,11 @@ oauth-sign@~0.9.0:
|
|||
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
|
||||
integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==
|
||||
|
||||
oauth@0.9.x:
|
||||
version "0.9.15"
|
||||
resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1"
|
||||
integrity sha1-vR/vr2hslrdUda7VGWQS/2DPucE=
|
||||
|
||||
object-assign@^4, object-assign@^4.1.0, object-assign@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||
|
@ -7276,6 +7281,61 @@ pascalcase@^0.1.1:
|
|||
resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14"
|
||||
integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=
|
||||
|
||||
passport-google-oauth1@1.x.x:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/passport-google-oauth1/-/passport-google-oauth1-1.0.0.tgz#af74a803df51ec646f66a44d82282be6f108e0cc"
|
||||
integrity sha1-r3SoA99R7GRvZqRNgigr5vEI4Mw=
|
||||
dependencies:
|
||||
passport-oauth1 "1.x.x"
|
||||
|
||||
passport-google-oauth20@2.x.x:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/passport-google-oauth20/-/passport-google-oauth20-2.0.0.tgz#0d241b2d21ebd3dc7f2b60669ec4d587e3a674ef"
|
||||
integrity sha512-KSk6IJ15RoxuGq7D1UKK/8qKhNfzbLeLrG3gkLZ7p4A6DBCcv7xpyQwuXtWdpyR0+E0mwkpjY1VfPOhxQrKzdQ==
|
||||
dependencies:
|
||||
passport-oauth2 "1.x.x"
|
||||
|
||||
passport-google-oauth@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/passport-google-oauth/-/passport-google-oauth-2.0.0.tgz#f6eb4bc96dd6c16ec0ecfdf4e05ec48ca54d4dae"
|
||||
integrity sha512-JKxZpBx6wBQXX1/a1s7VmdBgwOugohH+IxCy84aPTZNq/iIPX6u7Mqov1zY7MKRz3niFPol0KJz8zPLBoHKtYA==
|
||||
dependencies:
|
||||
passport-google-oauth1 "1.x.x"
|
||||
passport-google-oauth20 "2.x.x"
|
||||
|
||||
passport-oauth1@1.x.x:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/passport-oauth1/-/passport-oauth1-1.1.0.tgz#a7de988a211f9cf4687377130ea74df32730c918"
|
||||
integrity sha1-p96YiiEfnPRoc3cTDqdN8ycwyRg=
|
||||
dependencies:
|
||||
oauth "0.9.x"
|
||||
passport-strategy "1.x.x"
|
||||
utils-merge "1.x.x"
|
||||
|
||||
passport-oauth2@1.x.x:
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/passport-oauth2/-/passport-oauth2-1.5.0.tgz#64babbb54ac46a4dcab35e7f266ed5294e3c4108"
|
||||
integrity sha512-kqBt6vR/5VlCK8iCx1/KpY42kQ+NEHZwsSyt4Y6STiNjU+wWICG1i8ucc1FapXDGO15C5O5VZz7+7vRzrDPXXQ==
|
||||
dependencies:
|
||||
base64url "3.x.x"
|
||||
oauth "0.9.x"
|
||||
passport-strategy "1.x.x"
|
||||
uid2 "0.0.x"
|
||||
utils-merge "1.x.x"
|
||||
|
||||
passport-strategy@1.x.x:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4"
|
||||
integrity sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ=
|
||||
|
||||
passport@^0.4.1:
|
||||
version "0.4.1"
|
||||
resolved "https://registry.yarnpkg.com/passport/-/passport-0.4.1.tgz#941446a21cb92fc688d97a0861c38ce9f738f270"
|
||||
integrity sha512-IxXgZZs8d7uFSt3eqNjM9NQ3g3uQCW5avD8mRNoXV99Yig50vjuaez6dQK2qC0kVWPRTujxY0dWgGfT09adjYg==
|
||||
dependencies:
|
||||
passport-strategy "1.x.x"
|
||||
pause "0.0.1"
|
||||
|
||||
path-exists@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515"
|
||||
|
@ -7350,6 +7410,11 @@ path-type@^4.0.0:
|
|||
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
|
||||
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
|
||||
|
||||
pause@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/pause/-/pause-0.0.1.tgz#1d408b3fdb76923b9543d96fb4c9dfd535d9cb5d"
|
||||
integrity sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10=
|
||||
|
||||
pend@~1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
|
||||
|
@ -9433,6 +9498,11 @@ uid-safe@~2.1.5:
|
|||
dependencies:
|
||||
random-bytes "~1.0.0"
|
||||
|
||||
uid2@0.0.x:
|
||||
version "0.0.3"
|
||||
resolved "https://registry.yarnpkg.com/uid2/-/uid2-0.0.3.tgz#483126e11774df2f71b8b639dcd799c376162b82"
|
||||
integrity sha1-SDEm4Rd03y9xuLY53NeZw3YWK4I=
|
||||
|
||||
unc-path-regex@^0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa"
|
||||
|
@ -9625,7 +9695,7 @@ util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1:
|
|||
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
||||
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
|
||||
|
||||
utils-merge@1.0.1:
|
||||
utils-merge@1.0.1, utils-merge@1.x.x:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
|
||||
integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
|
||||
|
|
Loading…
Add table
Reference in a new issue