From 7478b176782d37331e1633e0f7a2e2b031331d85 Mon Sep 17 00:00:00 2001 From: Jeff Wilcox Date: Tue, 19 Jul 2016 16:12:23 -0700 Subject: [PATCH] Authentication refactor' --- middleware/errorHandler.js | 2 +- middleware/passport-routes.js | 4 + oss/index.js | 195 ++++++++++++++++++++---------- oss/user.js | 5 +- public/repos-css/oss.css | 16 +++ routes/index-authenticated.js | 26 ++-- routes/link-cleanup.js | 67 +++++++--- routes/link.js | 4 + routes/thanks.js | 5 +- utils.js | 20 +++ views/layout.jade | 27 +++-- views/link.jade | 2 +- views/multiplegithubaccounts.jade | 8 +- views/unlink.jade | 4 +- 14 files changed, 269 insertions(+), 116 deletions(-) diff --git a/middleware/errorHandler.js b/middleware/errorHandler.js index 67de1539..ad094002 100644 --- a/middleware/errorHandler.js +++ b/middleware/errorHandler.js @@ -62,6 +62,6 @@ module.exports = function (err, req, res, next) { error: {}, title: err.status === 404 ? 'Not Found' : 'Oops', user: req.user, - config: config, + config: config.obfuscatedConfig, }); }; diff --git a/middleware/passport-routes.js b/middleware/passport-routes.js index e1936ae9..65876e0d 100644 --- a/middleware/passport-routes.js +++ b/middleware/passport-routes.js @@ -38,6 +38,7 @@ module.exports = function configurePassport(app, passport, initialConfig) { app.get('/signin/github/join', (req, res) => { res.render('creategithubaccount', { title: 'Create a GitHub account', + user: req.user, }); }); @@ -80,6 +81,9 @@ module.exports = function configurePassport(app, passport, initialConfig) { delete req.user.github; } var url = req.headers.referer || '/'; + if (req.query.redirect === 'github') { + url = 'https://github.com/logout'; + } res.redirect(url); }); diff --git a/oss/index.js b/oss/index.js index 6801d8fc..0909ed56 100644 --- a/oss/index.js +++ b/oss/index.js @@ -13,8 +13,27 @@ const Team = require('./team'); const User = require('./user'); const RedisHelper = require('./redis'); -function OpenSourceUserContext(applicationConfiguration, dataClient, user, redisInstance, callback) { +function OpenSourceUserContext(options, callback) { var self = this; + self.displayNames = { + github: null, + }; + self.usernames = { + github: null, + azure: null, + }; + self.id = { + github: null, + }; + self.entities = { + link: null, + primaryMembership: null, + }; + var applicationConfiguration = options.config; + var dataClient = options.dataClient; + var redisInstance = options.redisClient; + var requestUser = options.request ? options.request.user : null; + var link = options.link; var modernUser; this.cache = { orgs: {}, @@ -26,6 +45,7 @@ function OpenSourceUserContext(applicationConfiguration, dataClient, user, redis this.createModernUser = function (id, login) { modernUser = new User(this, id); modernUser.login = login; + return modernUser; }; this.setting = function (name) { return applicationConfiguration[name]; @@ -36,89 +56,117 @@ function OpenSourceUserContext(applicationConfiguration, dataClient, user, redis this.redisClient = function () { return redisInstance; }; - this.requestUser = function () { - return user; - }; - this.safeConfigurationTemp = safeSettings(applicationConfiguration); - this.authenticated = { - github: user && user.github && user.github.id, - azure: user && user.azure && user.azure.username, - }; - this.entities = { - link: null, - primaryMembership: null, - }; - this.usernames = { - github: user && user.github && user.github.username ? user.github.username : undefined, - azure: user && user.azure && user.azure.username ? user.azure.username : undefined, - }; - this.id = { - github: user && user.github && user.github.id ? user.github.id.toString() : undefined, - }; - if (this.id.github) { - this.createModernUser(this.id.github, this.usernames.github); - } + this.safeConfiguration = safeSettings(applicationConfiguration); this.baseUrl = '/'; this.redis = new RedisHelper(this, applicationConfiguration.redis.prefix); - this.initializeBasics(function (initError) { - if (callback) { - return callback(initError, self); - } - }); + if (link && options.request) { + return callback(new Error('The context cannot be set from both a request and a link instance.')); + } + if (link) { + return setPropertiesFromLink(self, link, callback); + } + if (options.request) { + return this.resolveLinkFromRequest(options.request, callback); + } + callback(new Error('Could not initialize the context for the acting user.'), self); +} + +function setPropertiesFromLink(context, link, callback) { + context.usernames.github = link.ghu; + context.id.github = link.ghid.toString(); + context.usernames.azure = link.aadupn; + context.entities.link = link; + var modernUser = context.modernUser(); + if (!modernUser && context.id.github) { + modernUser = context.createModernUser(context.id.github, context.usernames.github); + } + modernUser.link = link; + callback(null, context); +} + +function tooManyLinksError(self, userLinks, callback) { + var tooManyLinksError = new Error(`This account has ${userLinks.length} linked GitHub accounts.`); + tooManyLinksError.links = userLinks; + tooManyLinksError.tooManyLinks = true; + return callback(tooManyLinksError, self); +} + +function existingGitHubIdentityError(self, link, requestUser, callback) { + var endUser = requestUser.azure.displayName || requestUser.azure.username; + var obfuscatedUsername = utils.obfuscate(link.ghu, 4); + var anotherGitHubAccountError = new Error(`${endUser}, there is a different GitHub account linked to your corporate identity.`); + anotherGitHubAccountError.anotherAccount = true; + anotherGitHubAccountError.detailed = `If you need to switch which account is associated with your identity, please unlink the old account first. Your other GitHub account username ends in: ${obfuscatedUsername}.`; + anotherGitHubAccountError.fancyLink = { + link: '/signout/github/?redirect=github', + title: `Sign Out ${requestUser.github.username} on GitHub`, + }; + return callback(anotherGitHubAccountError, self); } // ---------------------------------------------------------------------------- // Populate the user's OSS context object. // ---------------------------------------------------------------------------- -OpenSourceUserContext.prototype.initializeBasics = function (callback) { +OpenSourceUserContext.prototype.resolveLinkFromRequest = function (request, callback) { var self = this; - var requestUser = this.requestUser(); - if (!userObject && this.setting('primaryAuthenticationScheme') === 'aad' && requestUser.azure && requestUser.azure.username) { - return this.dataClient().getUserByAadUpn(requestUser.azure.username, function (findError, userLinks) { + var requestUser = request.user; + if (requestUser && requestUser.github) { + self.usernames.github = requestUser.github.username; + self.id.github = requestUser.github.id; + self.displayNames.github = requestUser.github.displayName; + } + if (requestUser && requestUser.azure) { + self.usernames.azure = requestUser.azure.username; + } + if (self.setting('primaryAuthenticationScheme') === 'aad' && requestUser.azure && requestUser.azure.username) { + return self.dataClient().getUserByAadUpn(requestUser.azure.username, function (findError, userLinks) { if (findError) { - // XXX: wrap with a useful message? - return callback(findError); + return callback(utils.wrapError(findError, 'There was a problem trying to load the link for the active user.'), self); } if (userLinks.length === 0) { - return callback(); + return callback(null, self); } if (userLinks.length > 1) { - var tooManyLinksError = new Error(`This account has ${userLinks.length} linked GitHub accounts.`); - tooManyLinksError.links = userLinks; - tooManyLinksError.tooManyLinks = true; - return callback(tooManyLinksError); + return tooManyLinksError(self, userLinks, callback); } var link = userLinks[0]; - self.usernames.github = link.ghu; - self.id.github = link.ghid.toString(); - self.createModernUser(self.id.github, self.usernames.github); - self.entities.link = link; - self.modernUser().link = link; - // todo: if their AAD name or upn has changed, but oid is still the same... schedule an update! - // question: should this.authenticated.github be true or false, since it isn't authenticated yet? - callback(null, false); + if (requestUser.github && requestUser.github.username && link.ghu !== requestUser.github.username) { + if (link.ghid === requestUser.github.id) { + // TODO: The user has changed their GitHub name, simply update the link record. + } else { + return existingGitHubIdentityError(self, link, requestUser, callback); + } + } + if (requestUser.azure.displayName !== link.aadname) { + // TODO: update their display name? + console.warn(`The user's Azure display name has changed. Link: ${link.aadname}, Passport: ${requestUser.azure.displayName}`); + } + if (requestUser.azure.username !== link.aadupn) { + // Confirm that their OID is the same + // RARE: TODO: ??? update their AAD upn in the link + // if OID has changed, freak out? + console.warn(`The user's Azure username has changed. Link: ${link.aadupn}, Passport: ${requestUser.azure.username}`); + } + return setPropertiesFromLink(self, link, callback); }); } - var userObject = this.modernUser(); + var userObject; + if (self.id.github) { + userObject = self.createModernUser(self.id.github, self.usernames.github); + } if (!userObject) { - return callback(new Error('There\'s a logic bug in the user context object. We cannot continue.')); + return callback(new Error('There\'s a logic bug in the user context object. We cannot continue.'), self); } userObject.getLink(function (error, link) { if (error) { - return callback(utils.wrapError(error, 'We were not able to retrieve information about any link for your user account at this time.')); + return callback(utils.wrapError(error, 'We were not able to retrieve information about any link for your user account at this time.'), self); } if (link) { - self.entities.link = link; + // CONSIDER: If the link values differ from the request properties... + return setPropertiesFromLink(self, link, callback); + } else { + callback(null, self); } - callback(null, false); - /*self.org().queryUserMembership(true, function (error, result) { - // CONSIDER: This is part of the isAdministrator updates... - if (result && result.state && result.role && result.role === 'admin') { - self.entities.primaryMembership = result; - } - callback(null, false); - }); - */ }); }; @@ -559,8 +607,7 @@ OpenSourceUserContext.prototype.saveUserAlert = function (req, message, title, c }; function safeSettings(config) { - // CONSIDER: IMPLEMENT. - return config; + return config.obfuscatedConfig; } // ---------------------------------------------------------------------------- @@ -578,11 +625,31 @@ OpenSourceUserContext.prototype.render = function (req, res, view, title, option if (breadcrumbs && breadcrumbs.length && breadcrumbs.length > 0) { breadcrumbs[breadcrumbs.length - 1].isLast = true; } + var authScheme = this.setting('primaryAuthenticationScheme'); + var user = { + primaryAuthenticationScheme: authScheme, + primaryUsername: authScheme === 'github' ? this.usernames.github : this.usernames.azure, + githubSignout: authScheme === 'github' ? '/signout' : '/signout/github', + azureSignout: authScheme === 'github' ? '/signout/azure' : '/signout', + }; + if (this.id.github || this.usernames.github) { + user.github = { + id: this.id.github, + username: this.usernames.github, + displayName: this.displayNames.github, + increasedScope: null, // TODO: Increased scope should be set in the pipeline + }; + } + if (this.usernames.azure) { + user.azure = { + username: this.usernames.azure, + }; + } var obj = { title: title, - config: this.safeConfigurationTemp, + config: this.safeConfiguration, serviceBanner: this.setting('serviceBanner'), - user: this.requestUser(), + user: user, ossLink: this.entities.link, showBreadcrumbs: true, breadcrumbs: breadcrumbs, diff --git a/oss/user.js b/oss/user.js index e79ef5e1..0a65e8cc 100644 --- a/oss/user.js +++ b/oss/user.js @@ -3,6 +3,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // +const async = require('async'); const utils = require('../utils'); const debug = require('debug')('azureossportal'); @@ -165,12 +166,12 @@ OpenSourceUser.prototype.unlinkAndDrop = function (callback) { if (getOrganizationsError) { return callback(getOrganizationsError); } - async.each(currentOrganizationMemberships, function (org, callback) { + async.each(currentOrganizationMemberships, function (org, cb) { org.removeUserMembership(function () { // TODO: We now continue with deletes when one fails. Common // failure case is when they have a pending invite, it will live // on... which is not ideal. - callback(); + cb(); }); }, function (/* ignored error per above statement */) { var dc = self.oss.dataClient(); diff --git a/public/repos-css/oss.css b/public/repos-css/oss.css index 1173fefa..7bc8a9d5 100644 --- a/public/repos-css/oss.css +++ b/public/repos-css/oss.css @@ -210,6 +210,22 @@ footer { margin-bottom: 16px; } +.pad, vertical-pad, .vertical-pad-top { + padding-top: 16px; +} + +.pad, vertical-pad, .vertical-pad-bottom { + padding-bottom: 16px; +} + +.pad, .horizontal-pad, .horizontal-pad-left { + padding-left: 16px; +} + +.pad, .horizontal-pad, .horizontal-pad-right { + padding-right: 16px; +} + code { color: #333; } diff --git a/routes/index-authenticated.js b/routes/index-authenticated.js index 1159f250..40494337 100644 --- a/routes/index-authenticated.js +++ b/routes/index-authenticated.js @@ -14,17 +14,17 @@ const linkCleanupRoute = require('./link-cleanup'); router.use(function (req, res, next) { var config = req.app.settings.runtimeConfig; if (req.isAuthenticated()) { - if (req.user && !req.user.github) { - // no github info - // IF secondary auth,... 1) check for a link - // return next(new Error('We do not have your GitHub stuff.')); + var expectedAuthenticationProperty = config.primaryAuthenticationScheme === 'github' ? 'github' : 'azure'; + if (req.user && !req.user[expectedAuthenticationProperty]) { + console.warn(`A user session was authenticated but did not have present the property ${expectedAuthenticationProperty} expected for this type of authentication. Signing them out.`); + return res.redirect('/signout'); } - if (req.user && req.user.github && !req.user.github.id) { - return next(new Error('Invalid GitHub user information provided by GitHub.')); + var expectedAuthenticationKey = config.primaryAuthenticationScheme === 'github' ? 'id' : 'username'; + if (!req.user[expectedAuthenticationProperty][expectedAuthenticationKey]) { + return next(new Error('Invalid information present for the authentication provider.')); } return next(); } - var url = req.originalUrl; if (url) { if (req.session) { @@ -36,9 +36,13 @@ router.use(function (req, res, next) { }); router.use((req, res, next) => { - var config = req.app.settings.runtimeConfig; - var dc = req.app.settings.dataclient; - new OpenSourceUserContext(config, dc, req.user, dc.cleanupInTheFuture.redisClient, function (error, instance) { + var options = { + config: req.app.settings.runtimeConfig, + dataClient: req.app.settings.dataclient, + redisClient: req.app.settings.dataclient.cleanupInTheFuture.redisClient, + request: req, + }; + new OpenSourceUserContext(options, (error, instance) => { req.oss = instance; if (error && error.tooManyLinks === true) { if (req.url === '/link-cleanup') { @@ -160,7 +164,7 @@ router.get('/', function (req, res, next) { onboarding = true; } var render = function (results) { - var pageTitle = results && results.userOrgMembership === false ? 'My GitHub Account' : config.companyName + ' - Open Source Portal for GitHub'; + var pageTitle = results && results.userOrgMembership === false ? 'My GitHub Account' : config.companyName + ' - ' + config.portalName; oss.render(req, res, 'index', pageTitle, { accountInfo: results, onboarding: onboarding, diff --git a/routes/link-cleanup.js b/routes/link-cleanup.js index 13ff5941..fd166172 100644 --- a/routes/link-cleanup.js +++ b/routes/link-cleanup.js @@ -10,6 +10,7 @@ const router = express.Router(); const async = require('async'); const moment = require('moment'); const utils = require('../utils'); +const OpenSourceUserContext = require('../oss'); // Enforcing just a single GitHub account per Active Directory user. With // mild refactoring, this portal could easily support a session selecting @@ -37,19 +38,20 @@ router.use((req, res, next) => { }); }); -function renderCleanupPage(req, res, optionalUsernameToConfirm) { +function renderCleanupPage(req, res, idToConfirm, links) { + links = links || req.linksForCleanup; let twoColumns = [[], []]; - for (let i = 0; i < req.linksForCleanup.length; i++) { - if (req.linksForCleanup[i].joined) { - req.linksForCleanup[i].joinedDate = new Date(Math.round(req.linksForCleanup[i].joined)); + for (let i = 0; i < links.length; i++) { + if (links[i].joined) { + links[i].joinedDate = new Date(Math.round(links[i].joined)); } - twoColumns[i % 2].push(req.linksForCleanup[i]); + twoColumns[i % 2].push(links[i]); } req.oss.render(req, res, 'multiplegithubaccounts', 'GitHub Cleanup', { linksForCleanupByColumn: twoColumns, numberToRemove: req.linksForCleanup.length - 1, - confirming: optionalUsernameToConfirm, - }) + confirming: idToConfirm, + }); } router.get('/', (req, res, next) => { @@ -57,20 +59,45 @@ router.get('/', (req, res, next) => { }); router.post('/', (req, res, next) => { - let username = req.body.unlink; - let isConfirming = req.body.confirm === username; - if (!isConfirming) { - return renderCleanupPage(req, res, username); + let id = req.body.unlink; + let link = null; + let remainingLinks = []; + for (let i = 0; i < req.linksForCleanup.length; i++) { + if (req.linksForCleanup[i].ghid === id) { + link = req.linksForCleanup[i]; + } else { + remainingLinks.push(req.linksForCleanup[i]); + } } - + if (!link) { + return next(new Error(`Could not identify the link for GitHub user ${id}.`)); + } + let isConfirming = req.body.confirm === id; + if (!isConfirming) { + return renderCleanupPage(req, res, id); + } + var options = { + config: req.app.settings.runtimeConfig, + dataClient: req.app.settings.dataclient, + redisClient: req.app.settings.dataclient.cleanupInTheFuture.redisClient, + link: link, + }; + new OpenSourceUserContext(options, function (contextError, unlinkContext) { + if (contextError) { + return next(contextError); + } + unlinkContext.modernUser().unlinkAndDrop((unlinkError) => { + if (unlinkError) { + return next(unlinkError); + } + if (remainingLinks.length > 1) { + renderCleanupPage(req, res, null, remainingLinks); + } else { + req.oss.saveUserAlert(req, link.ghu + ' has been unlinked. You now have just one GitHub account link.', 'Link cleanup complete', 'success'); + res.redirect('/'); + } + }); + }); }); -/* var link = userLinks[0]; - self.usernames.github = link.ghu; - self.id.github = link.ghid.toString(); - self.createModernUser(self.id.github, self.usernames.github); - self.entities.link = link; - self.modernUser().link = link; - */ - module.exports = router; diff --git a/routes/link.js b/routes/link.js index ed0191c0..43354b7a 100644 --- a/routes/link.js +++ b/routes/link.js @@ -45,7 +45,11 @@ router.post('/', function (req, res, next) { }); router.get('/update', function (req, res, next) { + var config = req.app.settings.runtimeConfig; var oss = req.oss; + if (config.primaryAuthenticationScheme === 'aad'){ + return next('Changing a GitHub account is not yet supported.'); + } if (!(oss.usernames.azure)) { return oss.render(req, res, 'linkUpdate', 'Update your account ' + oss.usernames.github + ' by signing in with corporate credentials.'); } diff --git a/routes/thanks.js b/routes/thanks.js index 3b8f8955..8669e0b2 100644 --- a/routes/thanks.js +++ b/routes/thanks.js @@ -28,14 +28,13 @@ function getPackageInfo() { } router.get('/', function (req, res) { - var config = req.app.settings.runtimeConfig; + var config = req.app.settings.runtimeConfig.obfuscatedConfig; var components = getPackageInfo(); res.render('thanks', { - user: req.user, config: config, components: components, serviceBanner: config && config.serviceBanner ? config.serviceBanner : undefined, - title: 'Open Source Portal for GitHub - ' + config.companyName + title: 'Open Source Components', }); }); diff --git a/utils.js b/utils.js index 2cc6ac7c..4fe89b44 100644 --- a/utils.js +++ b/utils.js @@ -160,6 +160,26 @@ exports.arrayToHashById = function athi(inputArray) { return hash; }; +// ---------------------------------------------------------------------------- +// Obfuscate a string value, optionally leaving a few characters visible. +// ---------------------------------------------------------------------------- +exports.obfuscate = function obfuscate(value, lastCharactersShowCount) { + if (value === undefined || value === null || value.length === undefined) { + return value; + } + var length = value.length; + lastCharactersShowCount = lastCharactersShowCount || 0; + lastCharactersShowCount = Math.min(lastCharactersShowCount, length - 1); + var obfuscated = ''; + for (var i = 0; i < length - lastCharactersShowCount; i++) { + obfuscated += '*'; + } + for (var j = length - lastCharactersShowCount; j < length; j++) { + obfuscated += value[j]; + } + return obfuscated; +} + // ---------------------------------------------------------------------------- // A very basic breadcrumb stack that ties in to an Express request object. // ---------------------------------------------------------------------------- diff --git a/views/layout.jade b/views/layout.jade index 1ea54721..0fe49e08 100644 --- a/views/layout.jade +++ b/views/layout.jade @@ -7,12 +7,12 @@ doctype html html(lang="en") head meta(charset='utf-8') - title= (user && user.github && user.github.username) ? title + ' - ' + user.github.username : title + title= (user && user.primaryUsername) ? title + ' - ' + user.primaryUsername : title meta(http-equiv='X-UA-Compatible', content='IE=edge') meta(name='viewport', content='width=device-width, initial-scale=1.0') meta(name='author', content='Microsoft Open Source') link(href='/css/bootstrap.min.css?1', rel='stylesheet') - link(href='/repos-css/oss.css?2a', rel='stylesheet') + link(href='/repos-css/oss.css?2b', rel='stylesheet') link(rel='shortcut icon', href='/favicon.ico') link(rel='apple-touch-icon', sizes='114x114,72x72,144x144,60x60,120x120,76x76,152x152,180x180', href='/favicon-144.png') meta(name='msapplication-config', content='none') @@ -73,6 +73,8 @@ html(lang="en") nav(role='navigation') div.container(style='margin-top:24px;margin-bottom:12px') div.row(style=(user && !error && ossLink) ? 'margin-left:0' : 'margin-left:-30px') + - var githubSignout = user && user.githubSignout ? user.githubSignout : '/signout' + - var azureSignout = user && user.azureSignout ? user.azureSignout : '/signout' div.col-md-6 if user && user.github h4 Your GitHub Account @@ -84,10 +86,16 @@ html(lang="en") img(alt=user.github.displayName, src=user.github.avatarUrl + '&s=80', style='margin-right:10px;width:30px;height:30px', data-user=user.github.id) a.btn.btn-sm.btn-muted(href='https://github.com/settings/profile', target='_new', title='Click to edit your public GitHub profile')= user.github.username a.btn.btn-sm.btn-muted-more(href='https://github.com/settings/profile', target='_new', title='Click to edit your public GitHub profile')= user.github.displayName || user.github.username - a.btn.btn-sm.btn-white(href='/signout', style='margin-left:10px') Sign Out + if primaryAuthenticationScheme == 'github' + a.btn.btn-sm.btn-white(href=githubSignout, style='margin-left:10px') Sign Out + else + a.btn.btn-sm.btn-white(href='/link/update', style='margin-left:10px') Change else p - small Sign in or create your GitHub.com account to manage your #{config && config.companyName || 'corporate'} open source identity. + if primaryAuthenticationScheme == 'github' + small Sign in or create your GitHub.com account to manage your #{config && config.companyName || 'corporate'} open source identity + else + small Sign in to GitHub p a.btn.btn-sm.btn-primary(href='/signin/github') Sign in div.col-md-6 @@ -98,16 +106,19 @@ html(lang="en") if ossLink.aadname a.btn.btn-sm.btn-muted(href='/signin/azure')= ossLink.aadname a.btn.btn-sm.btn-muted-more(href='/signin/azure')= ossLink.aadupn - a.btn.btn-sm.btn-white(href='/link/update', style='margin-left:10px') Change + if primaryAuthenticationScheme == 'github' + a.btn.btn-sm.btn-white(href='/link/update', style='margin-left:10px') Change + else + a.btn.btn-sm.btn-white(href=azureSignout, style='margin-left:10px') Sign Out else if user.azure //- NOTE: This is actually visually backward from the above link display... h4 Your #{config && config.companyName ? config.companyName : 'Corporate'} Identity p if user.azure.username - a.btn.btn-sm.btn-muted(href='/signout/azure')= user.azure.username + a.btn.btn-sm.btn-muted(href=azureSignout)= user.azure.username if user.azure.displayName a.btn.btn-sm.btn-muted-more(href='/signout/azure')= user.azure.displayName - a.btn.btn-sm.btn-white(href='/signout/azure', style='margin-left:10px') Sign Out + a.btn.btn-sm.btn-white(href=azureSignout, style='margin-left:10px') Sign Out //- Just show breadcrumbs when there is an interesting path available if showBreadcrumbs === true && breadcrumbs && breadcrumbs.length && breadcrumbs.length > 1 div.container @@ -151,6 +162,8 @@ html(lang="en") h4 | Storage:  span.bg-danger(style='color:white;padding:2px')= config.azureStorage.account + h4 + | Storage Prefix: #{config.azureStorage.prefix} if config && config.redis h4 | Redis:  diff --git a/views/link.jade b/views/link.jade index 7be484e6..43288433 100644 --- a/views/link.jade +++ b/views/link.jade @@ -27,7 +27,7 @@ block content tr - gitAlertClass = config.primaryAuthenticationScheme == 'github' ? '' : 'alert-success' - aadAlertClass = config.primaryAuthenticationScheme == 'aad' ? '' : 'alert-success' - td(class=gitAlertClass)= user.github.displayUsernameTemporary || user.github.username + td(class=gitAlertClass)= user.github.username td div.text-center i.glyphicon.glyphicon-link diff --git a/views/multiplegithubaccounts.jade b/views/multiplegithubaccounts.jade index c8fb9d66..b0ffcaff 100644 --- a/views/multiplegithubaccounts.jade +++ b/views/multiplegithubaccounts.jade @@ -17,7 +17,7 @@ block content each column in linksForCleanupByColumn div.col-sm-6.col-md-6.col-lg-6 each link in column - div(style='border:4px solid #ddd;padding:0 24px 0 16px') + div.horizontal-pad.vertical-space(style='border:4px solid #ddd') div h2= link.ghu p GitHub Account @@ -25,11 +25,11 @@ block content | View #{link.ghu} GitHub Profile   i.glyphicon.glyphicon-share-alt form(method='post').vertical-space - input(type='hidden', name='unlink', value=link.ghu) - - var isConfirming = confirming === link.ghu + input(type='hidden', name='unlink', value=link.ghid) + - var isConfirming = confirming === link.ghid - var buttonClass = isConfirming ? 'btn-danger' : 'btn-default' if isConfirming - input(type='hidden', name='confirm', value=link.ghu) + input(type='hidden', name='confirm', value=link.ghid) p.text-danger Please confirm that you are ready to unlink this GitHub account. It will lose access to any and all corporate organizations, teams and private repos. input.btn.btn-lg(class=buttonClass, type='submit', value=isConfirming ? 'Yes, unlink #{link.ghu}' : 'Unlink #{link.ghu}') if link.joinedDate diff --git a/views/unlink.jade b/views/unlink.jade index d845dfe6..c55fd971 100644 --- a/views/unlink.jade +++ b/views/unlink.jade @@ -32,10 +32,8 @@ block content li Private forks of repos from these orgs, if any, will be removed by GitHub li Work committed in a private fork of a #{config.companyName} org will be lost - p If you have any questions about this, please contact your team's GitHub liasons or your corporate legal contact. - form(method='post', action='/unlink') p - input.btn.btn-lg.btn-primary(type='submit', value='Remove my corporate link') + input.btn.btn-lg.btn-danger(type='submit', value='Remove my corporate link') |       a.btn.btn-lg.btn-default(href='/') Cancel