You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@helix.apache.org by ne...@apache.org on 2022/10/19 18:36:52 UTC
[helix] branch master updated: Add support for optional identity token source in helix-front
This is an automated email from the ASF dual-hosted git repository.
nealsun pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/helix.git
The following commit(s) were added to refs/heads/master by this push:
new cfa08e481 Add support for optional identity token source in helix-front
cfa08e481 is described below
commit cfa08e481ad2b95aee0c82b0d81368faa660a0ce
Author: Micah Stubbs <mi...@gmail.com>
AuthorDate: Wed Oct 19 11:36:47 2022 -0700
Add support for optional identity token source in helix-front
During helix-front login, optionally request an identity token
from a configurable identity token API.
---
helix-front/package.json | 6 +-
helix-front/server/.gitignore | 1 +
helix-front/server/config.example.ts | 44 ++++++++++++++
helix-front/server/config.ts | 24 --------
helix-front/server/controllers/d.ts | 4 ++
helix-front/server/controllers/user.ts | 101 ++++++++++++++++++++++++++++-----
6 files changed, 139 insertions(+), 41 deletions(-)
diff --git a/helix-front/package.json b/helix-front/package.json
index 9e9363095..cc125e5c3 100644
--- a/helix-front/package.json
+++ b/helix-front/package.json
@@ -22,7 +22,7 @@
"scripts": {
"ng": "ng",
"build": "rm -rf dist && mkdir dist && ng build --aot --configuration production && tsc -p server",
- "build:dev": "rm -rf dist && mkdir dist && ng build --aot --configuration development && tsc -p server",
+ "build:dev": "rm -rf dist && mkdir dist && yarn check:config && ng build --aot --configuration development && tsc -p server",
"start": "concurrently -r \"ng serve\" \"tsc -w -p server\" \"nodemon dist/server/app.js\"",
"prod": "yarn run build && node dist/server/app.js",
"test": "ng test",
@@ -40,7 +40,9 @@
"type:check:watch": "yarn type:check -- --watch",
"format:check": "npx prettier --check .",
"format": "yarn prettier --write .",
- "prepack": "yarn build"
+ "prepack": "yarn build",
+ "copy:config": "cp server/config.example.ts server/config.ts",
+ "check:config": "test -f server/config.ts || yarn copy:config"
},
"browserslist": [
"last 2 Edge versions",
diff --git a/helix-front/server/.gitignore b/helix-front/server/.gitignore
new file mode 100644
index 000000000..c3c165302
--- /dev/null
+++ b/helix-front/server/.gitignore
@@ -0,0 +1 @@
+config.ts
diff --git a/helix-front/server/config.example.ts b/helix-front/server/config.example.ts
new file mode 100644
index 000000000..f069dc4b2
--- /dev/null
+++ b/helix-front/server/config.example.ts
@@ -0,0 +1,44 @@
+export const HELIX_ENDPOINTS = {
+ helix: [
+ {
+ default: 'http://localhost:8100/admin/v2',
+ },
+ ],
+};
+export const SESSION_STORE = undefined;
+export const SSL = {
+ port: 0,
+ keyfile: '',
+ certfile: '',
+ passfile: '',
+ cafiles: [],
+};
+export const LDAP = {
+ uri: 'ldap://example.com',
+ base: 'DC=example,DC=com',
+ principalSuffix: '@example.com',
+ adminGroup: 'admin',
+};
+
+/**
+ * The url of your Identity Token API.
+ * This an API that should expect LDAP credentials
+ * and if the LDAP credentials are valid
+ * respond with a unique token of some kind.
+ */
+export const IDENTITY_TOKEN_SOURCE: string | undefined = undefined; // 'www.example.com';
+
+/**
+ * Any custom object that you would like
+ * to include in the body of the request
+ * to your custom identity source.
+ */
+export const CUSTOM_IDENTITY_TOKEN_REQUEST_BODY: any = {};
+
+/**
+ * This is the key that helix-front uses
+ * to access the token itself
+ * from the custom identity token response
+ * sent by your Identity Token API.
+ */
+export const TOKEN_RESPONSE_KEY: string = 'token';
diff --git a/helix-front/server/config.ts b/helix-front/server/config.ts
deleted file mode 100644
index cd93f6be6..000000000
--- a/helix-front/server/config.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-export const HELIX_ENDPOINTS = {
- helix: [
- {
- default: 'http://localhost:8100/admin/v2',
- },
- ],
-};
-
-export const SESSION_STORE = undefined;
-
-export const SSL = {
- port: 0,
- keyfile: '',
- certfile: '',
- passfile: '',
- cafiles: [],
-};
-
-export const LDAP = {
- uri: 'ldap://example.com',
- base: 'DC=example,DC=com',
- principalSuffix: '@example.com',
- adminGroup: 'admin',
-};
diff --git a/helix-front/server/controllers/d.ts b/helix-front/server/controllers/d.ts
index 79334f05a..3f3c88415 100644
--- a/helix-front/server/controllers/d.ts
+++ b/helix-front/server/controllers/d.ts
@@ -5,6 +5,10 @@ export interface HelixUserRequest extends Request {
}
interface HelixSession {
+ // since this token is from a configurable
+ // identity source, the format really is
+ // `any` from helix-front's point of view.
+ identityToken: any;
username: string;
isAdmin: boolean;
}
diff --git a/helix-front/server/controllers/user.ts b/helix-front/server/controllers/user.ts
index 54dd5ac49..9a317b5ff 100644
--- a/helix-front/server/controllers/user.ts
+++ b/helix-front/server/controllers/user.ts
@@ -1,7 +1,12 @@
-import { Request, Response, Router } from 'express';
+import { Response, Router } from 'express';
import * as LdapClient from 'ldapjs';
+import * as request from 'request';
-import { LDAP } from '../config';
+import {
+ LDAP,
+ IDENTITY_TOKEN_SOURCE,
+ CUSTOM_IDENTITY_TOKEN_REQUEST_BODY,
+} from '../config';
import { HelixUserRequest } from './d';
export class UserCtrl {
@@ -13,8 +18,11 @@ export class UserCtrl {
}
protected authorize(req: HelixUserRequest, res: Response) {
- // you can rewrite this function to support your own authorization logic
- // by default, doing nothing but redirection
+ //
+ // you can rewrite this function
+ // to support your own authorization logic
+ // by default, do nothing but redirect
+ //
if (req.query.url) {
res.redirect(req.query.url as string);
} else {
@@ -26,19 +34,26 @@ export class UserCtrl {
res.json(req.session.username || 'Sign In');
}
+ //
+ // Check the server-side session store,
+ // see if this helix-front ExpressJS server
+ // already knows that the current user is an admin.
+ //
protected can(req: HelixUserRequest, res: Response) {
try {
return res.json(req.session.isAdmin ? true : false);
} catch (err) {
- // console.log('error from can', err)
+ throw new Error(
+ `Error from /can logged in admin user session status endpoint: ${err}`
+ );
return false;
}
}
- protected login(request: HelixUserRequest, response: Response) {
- const credential = request.body;
+ protected login(req: HelixUserRequest, res: Response) {
+ const credential = req.body;
if (!credential.username || !credential.password) {
- response.status(401).json(false);
+ res.status(401).json(false);
return;
}
@@ -49,9 +64,9 @@ export class UserCtrl {
credential.password,
(err) => {
if (err) {
- response.status(401).json(false);
+ res.status(401).json(false);
} else {
- // login success
+ // LDAP login success
const opts = {
filter:
'(&(sAMAccountName=' +
@@ -60,6 +75,9 @@ export class UserCtrl {
scope: 'sub',
};
+ req.session.username = credential.username;
+ res.set('Username', credential.username);
+
ldap.search(LDAP.base, opts, function (err, result) {
let isInAdminGroup = false;
result.on('searchEntry', function (entry) {
@@ -69,14 +87,67 @@ export class UserCtrl {
const groupName = group.split(',', 1)[0].split('=')[1];
if (groupName == LDAP.adminGroup) {
isInAdminGroup = true;
- break;
+
+ //
+ // Get an Identity-Token
+ // if an IDENTITY_TOKEN_SOURCE
+ // is specified in the config
+ //
+ if (IDENTITY_TOKEN_SOURCE) {
+ const body = JSON.stringify({
+ username: credential.username,
+ password: credential.password,
+ ...CUSTOM_IDENTITY_TOKEN_REQUEST_BODY,
+ });
+
+ const options = {
+ url: IDENTITY_TOKEN_SOURCE,
+ json: '',
+ body,
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ agentOptions: {
+ rejectUnauthorized: false,
+ },
+ };
+
+ function callback(error, _res, body) {
+ if (error) {
+ throw new Error(
+ `Failed to get ${IDENTITY_TOKEN_SOURCE} Token: ${error}`
+ );
+ } else if (body?.error) {
+ throw new Error(body?.error);
+ } else {
+ const parsedBody = JSON.parse(body);
+ req.session.isAdmin = isInAdminGroup;
+ req.session.identityToken = parsedBody;
+ //
+ // TODO possibly also send identity token
+ // TODO parsedBody to the client as a cookie
+ // TODO Github issue #2236
+ //
+ res.set('Identity-Token-Payload', body);
+ res.json(isInAdminGroup);
+
+ return parsedBody;
+ }
+ }
+ request.post(options, callback);
+ } else {
+ req.session.isAdmin = isInAdminGroup;
+ res.json(isInAdminGroup);
+ }
+ //
+ // END Get an Identity-Token
+ //
}
}
+ } else {
+ req.session.isAdmin = isInAdminGroup;
+ res.json(isInAdminGroup);
}
-
- request.session.username = credential.username;
- request.session.isAdmin = isInAdminGroup;
- response.json(isInAdminGroup);
});
});
}