This commit is contained in:
morsh 2017-05-25 17:04:13 +03:00
Родитель 5527dff430
Коммит ae050c0fcc
12 изменённых файлов: 181 добавлений и 89 удалений

Просмотреть файл

@ -1,6 +1,6 @@
{ {
"main.css": "static/css/main.b2105435.css", "main.css": "static/css/main.b2105435.css",
"main.css.map": "static/css/main.b2105435.css.map", "main.css.map": "static/css/main.b2105435.css.map",
"main.js": "static/js/main.164150ed.js", "main.js": "static/js/main.0fd897f0.js",
"main.js.map": "static/js/main.164150ed.js.map" "main.js.map": "static/js/main.0fd897f0.js.map"
} }

Просмотреть файл

@ -1 +1 @@
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="shortcut icon" href="/favicon.ico"><link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"><link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500"/><title>React App</title><link href="/static/css/main.b2105435.css" rel="stylesheet"></head><body><div id="root"></div><script type="text/javascript" src="/static/js/main.164150ed.js"></script></body></html> <!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="shortcut icon" href="/favicon.ico"><link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"><link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500"/><title>React App</title><link href="/static/css/main.b2105435.css" rel="stylesheet"></head><body><div id="root"></div><script type="text/javascript" src="/static/js/main.0fd897f0.js"></script></body></html>

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -12,6 +12,13 @@ const cosmosDBRouter = require('./routes/cosmos-db');
const azureRouter = require('./routes/azure'); const azureRouter = require('./routes/azure');
const app = express(); const app = express();
app.use((req, res, next) => {
console.log(`Request URL: ${req.url}`);
return next();
});
app.use(cookieParser()); app.use(cookieParser());
app.use(expressSession({ secret: 'keyboard cat', resave: true, saveUninitialized: false })); app.use(expressSession({ secret: 'keyboard cat', resave: true, saveUninitialized: false }));
app.use(bodyParser.json()); app.use(bodyParser.json());

Просмотреть файл

@ -1,7 +1,7 @@
{ {
"identityMetadata": "https://login.microsoftonline.com/common/.well-known/openid-configuration", "identityMetadata": "https://login.microsoftonline.com/common/.well-known/openid-configuration",
"validateIssuer": false, "validateIssuer": true,
"skipUserProfile": true, "skipUserProfile": false,
"responseType": "id_token code", "responseType": "id_token code",
"responseMode": "query" "responseMode": "query"
} }

Просмотреть файл

@ -279,8 +279,6 @@ router.post('/setup', (req, res) => {
} }
var content = (req.body && req.body.json) || ''; var content = (req.body && req.body.json) || '';
console.dir(content);
fs.writeFile(path.join(__dirname, '..', 'config', 'setup.private.json'), content, err => { fs.writeFile(path.join(__dirname, '..', 'config', 'setup.private.json'), content, err => {
if (err) { if (err) {
console.error(err); console.error(err);

Просмотреть файл

@ -21,8 +21,9 @@ if (fs.existsSync(path.join(__dirname, '..', 'config', 'setup.private.json'))) {
let users = []; let users = [];
function findByEmail(email, fn) { function findByEmail(email, fn) {
for (var i = 0, len = users.length; i < len; i++) { for (var i = 0, len = users.length; i < len; i++) {
var user = users[i]; let user = users[i];
console.info('we are using user: ', user);
console.info(`Current logged in user: ${user}`);
if (user.email === email) { if (user.email === email) {
return fn(null, user); return fn(null, user);
} }
@ -66,15 +67,18 @@ function initializePassport() {
skipUserProfile: configAuth.skipUserProfile, skipUserProfile: configAuth.skipUserProfile,
responseType: configAuth.responseType, responseType: configAuth.responseType,
responseMode: configAuth.responseMode, responseMode: configAuth.responseMode,
validateIssuer: configAuth.validateIssuer validateIssuer: configAuth.validateIssuer,
issuer: configSetup.issuer
}, },
function(iss, sub, profile, accessToken, refreshToken, done) { function(iss, sub, profile, accessToken, refreshToken, done) {
console.log(`passport arguments: ${arguments}`);
profile.email = profile.email || profile.upn; profile.email = profile.email || profile.upn;
if (!profile.email) { if (!profile.email) {
return done(new Error("No email found"), null); return done(new Error("No email found"), null);
} }
// asynchronous verification, for effect... // asynchronous verification, for effect...
process.nextTick(function () { process.nextTick(function () {
findByEmail(profile.email, function(err, user) { findByEmail(profile.email, function(err, user) {
@ -141,7 +145,11 @@ router.get('/account', (req, res) => {
function addAuthRoutes() { function addAuthRoutes() {
router.get('/login', router.get('/login',
passport.authenticate('azuread-openidconnect', { failureRedirect: '/login' }), (req, res, next) => {
console.log('aaa');
return next();
},
passport.authenticate('azuread-openidconnect', { failureRedirect: '/auth/login' }),
function(req, res) { function(req, res) {
console.info('Login was called in the Sample'); console.info('Login was called in the Sample');
res.redirect('/'); res.redirect('/');
@ -154,7 +162,7 @@ function addAuthRoutes() {
// provider will redirect the user back to this application at // provider will redirect the user back to this application at
// /auth/openid/return // /auth/openid/return
router.get('/openid', router.get('/openid',
passport.authenticate('azuread-openidconnect', { failureRedirect: '/login' }), passport.authenticate('azuread-openidconnect', { failureRedirect: '/auth/login' }),
function(req, res) { function(req, res) {
console.info('Authentication was called in the Sample'); console.info('Authentication was called in the Sample');
res.redirect('/'); res.redirect('/');
@ -166,7 +174,7 @@ function addAuthRoutes() {
// login page. Otherwise, the primary route function function will be called, // login page. Otherwise, the primary route function function will be called,
// which, in this example, will redirect the user to the home page. // which, in this example, will redirect the user to the home page.
router.get('/openid/return', router.get('/openid/return',
passport.authenticate('azuread-openidconnect', { failureRedirect: '/login' }), passport.authenticate('azuread-openidconnect', { failureRedirect: '/auth/login' }),
function(req, res) { function(req, res) {
console.info('We received a return from AzureAD.'); console.info('We received a return from AzureAD.');
res.redirect(redirectPath || '/'); res.redirect(redirectPath || '/');
@ -179,7 +187,7 @@ function addAuthRoutes() {
// login page. Otherwise, the primary route function function will be called, // login page. Otherwise, the primary route function function will be called,
// which, in this example, will redirect the user to the home page. // which, in this example, will redirect the user to the home page.
router.post('/openid/return', router.post('/openid/return',
passport.authenticate('azuread-openidconnect', { failureRedirect: '/login' }), passport.authenticate('azuread-openidconnect', { failureRedirect: '/auth/login' }),
function(req, res) { function(req, res) {
console.info('We received a return from AzureAD.'); console.info('We received a return from AzureAD.');
res.redirect(redirectPath || '/'); res.redirect(redirectPath || '/');
@ -191,7 +199,10 @@ function addAuthRoutes() {
res.redirect('/'); res.redirect('/');
}); });
} }
if (authEnabled) { addAuthRoutes(); } if (authEnabled) {
console.log(`Registering auth routes`);
addAuthRoutes();
}
/** /**
* Adding all authentication middleware on process start. * Adding all authentication middleware on process start.

Просмотреть файл

@ -46,6 +46,7 @@ export default class Home extends React.Component<any, IHomeState> {
redirectUrl: '', redirectUrl: '',
clientID: '', clientID: '',
clientSecret: '', clientSecret: '',
issuer: '',
loaded: false, loaded: false,
templates: [], templates: [],

Просмотреть файл

@ -6,11 +6,13 @@ import Button from 'react-md/lib/Buttons/Button';
import Switch from 'react-md/lib/SelectionControls/Switch'; import Switch from 'react-md/lib/SelectionControls/Switch';
import InfoDrawer from '../common/InfoDrawer'; import InfoDrawer from '../common/InfoDrawer';
import { ToastActions } from '../Toast';
import SetupActions from '../../actions/SetupActions'; import SetupActions from '../../actions/SetupActions';
import SetupStore from '../../stores/SetupStore'; import SetupStore, { ISetupStoreState } from '../../stores/SetupStore';
interface ISetupState extends ISetupConfig { interface ISetupState extends ISetupConfig {
editedEmail?: string;
validEmail?: boolean; validEmail?: boolean;
loaded?: boolean; loaded?: boolean;
} }
@ -21,33 +23,44 @@ export default class Setup extends React.Component<any, ISetupState> {
admins: null, admins: null,
stage: 'none', stage: 'none',
enableAuthentication: false, enableAuthentication: false,
editedEmail: '',
validEmail: true, validEmail: true,
allowHttp: false, allowHttp: false,
redirectUrl: '', redirectUrl: '',
clientID: '', clientID: '',
clientSecret: '', clientSecret: '',
loaded: false loaded: false,
issuer: ''
}; };
constructor(props: any) { constructor(props: any) {
super(props); super(props);
this.checkKeyValue = this.checkKeyValue.bind(this); this.updateSetupState = this.updateSetupState.bind(this);
this.checkEmailValue = this.checkEmailValue.bind(this);
this.onSave = this.onSave.bind(this); this.onSave = this.onSave.bind(this);
this.onCancel = this.onCancel.bind(this); this.onCancel = this.onCancel.bind(this);
this.onRemoveAdmin = this.onRemoveAdmin.bind(this); this.onRemoveAdmin = this.onRemoveAdmin.bind(this);
this.onSwitchAllowHttp = this.onSwitchAllowHttp.bind(this);
this.onSwitchAuthenticationEnables = this.onSwitchAuthenticationEnables.bind(this); this.onSwitchAuthenticationEnables = this.onSwitchAuthenticationEnables.bind(this);
this.onFieldChange = this.onFieldChange.bind(this); this.onFieldChange = this.onFieldChange.bind(this);
this.getAdminArray = this.getAdminArray.bind(this);
}
updateSetupState(state: ISetupStoreState) {
this.setState(state);
} }
componentDidMount() { componentDidMount() {
this.setState(SetupStore.getState()); this.updateSetupState(SetupStore.getState());
SetupActions.load(); SetupActions.load();
SetupStore.listen(state => { SetupStore.listen(this.updateSetupState);
this.setState(state); }
});
componentWillUnmount() {
SetupStore.unlisten(this.updateSetupState);
} }
validateEmail(email: string): boolean { validateEmail(email: string): boolean {
@ -56,7 +69,10 @@ export default class Setup extends React.Component<any, ISetupState> {
return re.test(email); return re.test(email);
} }
checkKeyValue(e: any) { checkEmailValue(e: any) {
this.setState({ editedEmail: e.target.value })
if (e.key === 'Enter') { if (e.key === 'Enter') {
let email = e.target.value; let email = e.target.value;
@ -75,15 +91,60 @@ export default class Setup extends React.Component<any, ISetupState> {
return true; return true;
} }
onSave () { fixRedirectUrl(redirectUrl: string): string {
if (redirectUrl) { return redirectUrl; }
let host = window.location.host;
// On localhost, authentication requests go directly to port 4000
if (host === 'localhost:3000') { host = 'localhost:4000'; }
return window.location.protocol + '//' + host + '/auth/openid/return';
}
getAdminArray(): string[] {
let admins = this.state.admins || [];
if (this.state.editedEmail) {
admins.push(this.state.editedEmail);
}
return admins;
}
onSave(): any {
let admins = this.getAdminArray();
let redirectUrl = this.fixRedirectUrl(this.state.redirectUrl);
if (this.state.enableAuthentication) {
if (!admins || !admins.length) {
return ToastActions.addToast({ text: 'Fill in at least one admin', action: null });
}
if (!redirectUrl) {
return ToastActions.addToast({ text: 'Fill in redirect url', action: null });
}
if (!this.state.issuer) {
return ToastActions.addToast({ text: 'Fill in issuer', action: null });
}
if (!this.state.clientID) {
return ToastActions.addToast({ text: 'Fill in client ID', action: null });
}
if (!this.state.clientSecret) {
return ToastActions.addToast({ text: 'Fill in client secret', action: null });
}
if (!this.state.allowHttp && redirectUrl.startsWith('http:')) {
return ToastActions.addToast({ text: 'Redirect url should start with https or enable http redirects', action: null });
}
}
var setupConfig = { var setupConfig = {
admins: this.state.admins, admins: admins,
stage: this.state.stage, stage: this.state.stage,
enableAuthentication: this.state.enableAuthentication, enableAuthentication: this.state.enableAuthentication,
allowHttp: this.state.allowHttp, allowHttp: this.state.allowHttp,
redirectUrl: this.state.redirectUrl, redirectUrl: redirectUrl,
clientID: this.state.clientID, clientID: this.state.clientID,
clientSecret: this.state.clientSecret clientSecret: this.state.clientSecret,
issuer: this.state.issuer
}; };
SetupActions.save(setupConfig, () => { window.location.replace('/'); }); SetupActions.save(setupConfig, () => { window.location.replace('/'); });
} }
@ -118,11 +179,10 @@ export default class Setup extends React.Component<any, ISetupState> {
render() { render() {
let { admins, loaded, validEmail, enableAuthentication, redirectUrl, clientID, clientSecret } = this.state; let { admins, loaded, validEmail, enableAuthentication, redirectUrl, clientID, clientSecret, issuer } = this.state;
if (!redirectUrl) { // Setting default redirect parameter
redirectUrl = window.location.protocol + '//' + window.location.host + '/auth/openid/return'; redirectUrl = this.fixRedirectUrl(redirectUrl);
}
if (!loaded) { if (!loaded) {
return null; return null;
@ -143,6 +203,7 @@ export default class Setup extends React.Component<any, ISetupState> {
<div style={{ width: '100%' }}> <div style={{ width: '100%' }}>
<Switch <Switch
id="enableAuthentication" id="enableAuthentication"
name="enableAuthentication"
label="Enable Authentication" label="Enable Authentication"
checked={enableAuthentication} checked={enableAuthentication}
onChange={this.onSwitchAuthenticationEnables} onChange={this.onSwitchAuthenticationEnables}
@ -171,6 +232,7 @@ export default class Setup extends React.Component<any, ISetupState> {
<div> <div>
<Switch <Switch
id="allowHttp" id="allowHttp"
name="allowHttp"
label="Allow http in authentication responses" label="Allow http in authentication responses"
checked={this.state.allowHttp} checked={this.state.allowHttp}
onChange={this.onSwitchAllowHttp} onChange={this.onSwitchAllowHttp}
@ -182,11 +244,11 @@ export default class Setup extends React.Component<any, ISetupState> {
id="adminEmail" id="adminEmail"
label="Administrator Email" label="Administrator Email"
error={!validEmail} error={!validEmail}
errorText={!validEmail && 'Please enter a valid email address'} errorText={(!validEmail && 'Please enter a valid email address') || ''}
lineDirection="center" lineDirection="center"
placeholder="Enter an additional administrator email" placeholder="Enter an additional administrator email"
className="md-cell md-cell--bottom" className="md-cell md-cell--bottom"
onKeyDown={this.checkKeyValue} onKeyDown={this.checkEmailValue}
/> />
<TextField <TextField
id="redirectUrl" id="redirectUrl"
@ -216,6 +278,15 @@ export default class Setup extends React.Component<any, ISetupState> {
defaultValue={clientSecret} defaultValue={clientSecret}
onChange={this.onFieldChange} onChange={this.onFieldChange}
/> />
<TextField
id="issuer"
label="Issuer: https://sts.windows.net/{Tenant-ID}/"
lineDirection="center"
placeholder="https://sts.windows.net/{Tenant-ID}/"
className="md-cell md-cell--bottom"
defaultValue={issuer}
onChange={this.onFieldChange}
/>
</div>) </div>)
} }
<Button flat primary label="Save &amp; Apply" onClick={this.onSave}>save</Button> <Button flat primary label="Save &amp; Apply" onClick={this.onSave}>save</Button>

Просмотреть файл

@ -5,7 +5,7 @@ import connections from '../data-sources/connections';
import { DataSourceConnector, IDataSourceDictionary } from '../data-sources'; import { DataSourceConnector, IDataSourceDictionary } from '../data-sources';
import SetupActions from '../actions/SetupActions'; import SetupActions from '../actions/SetupActions';
interface ISetupStoreState extends ISetupConfig { export interface ISetupStoreState extends ISetupConfig {
loaded: boolean; loaded: boolean;
saveSuccess: boolean; saveSuccess: boolean;
} }
@ -19,6 +19,7 @@ class SetupStore extends AbstractStoreModel<ISetupStoreState> implements ISetupS
redirectUrl: string; redirectUrl: string;
clientID: string; clientID: string;
clientSecret: string; clientSecret: string;
issuer: string;
loaded: boolean; loaded: boolean;
saveSuccess: boolean; saveSuccess: boolean;
@ -34,6 +35,7 @@ class SetupStore extends AbstractStoreModel<ISetupStoreState> implements ISetupS
this.clientSecret = ''; this.clientSecret = '';
this.loaded = false; this.loaded = false;
this.saveSuccess = false; this.saveSuccess = false;
this.issuer = '';
this.bindListeners({ this.bindListeners({
load: SetupActions.load load: SetupActions.load
@ -48,6 +50,7 @@ class SetupStore extends AbstractStoreModel<ISetupStoreState> implements ISetupS
this.redirectUrl = setupConfig.redirectUrl; this.redirectUrl = setupConfig.redirectUrl;
this.clientID = setupConfig.clientID; this.clientID = setupConfig.clientID;
this.clientSecret = setupConfig.clientSecret; this.clientSecret = setupConfig.clientSecret;
this.issuer = setupConfig.issuer;
this.loaded = true; this.loaded = true;
this.saveSuccess = true; this.saveSuccess = true;

1
src/types.d.ts поставляемый
Просмотреть файл

@ -155,4 +155,5 @@ interface ISetupConfig {
redirectUrl: string; redirectUrl: string;
clientID: string; clientID: string;
clientSecret: string; clientSecret: string;
issuer: string;
} }