Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OIDC Integration (OpenID) example #559

Open
vsychov opened this issue Apr 7, 2023 · 2 comments
Open

OIDC Integration (OpenID) example #559

vsychov opened this issue Apr 7, 2023 · 2 comments

Comments

@vsychov
Copy link

vsychov commented Apr 7, 2023

Hello,

Because almost all pull requests to this repo stay ignored by maintainer for a long time, I'm creating this as issue, that may helpful for someone else. This is PoC integration oauth2-proxy, that allow to use OpenId providers (list can be found at https://oauth2-proxy.github.io/oauth2-proxy/docs/configuration/oauth_provider) with this app. PoC uses docker-compose, but can easy back ported to k8s or etc.

This is steps works on current master revision.

Index: lib/model/db/company.js
===================================================================
diff --git a/lib/model/db/company.js b/lib/model/db/company.js
--- a/lib/model/db/company.js	(revision e0ca678927862600b65d22e090be92f608373f7f)
+++ b/lib/model/db/company.js	(date 1680865639019)
@@ -84,7 +84,7 @@
       comment      : "Indicate which mode the company account is in.",
     },
     timezone : {
-      type         : DataTypes.TEXT,
+      type         : DataTypes.STRING,
       allowNull    : true,
       defaultValue : 'Europe/London',
       comment      : 'Timezone current company is located in',
  • File lib/middleware/oidc_handler.js should be created:
"use strict";

module.exports = async function (req, res, next) {
    const oidcEmail = req?.headers?.["x-forwarded-email"].toLowerCase();
    const oidcName = req?.headers?.["x-forwarded-user"];
    const oidcGroups = req?.headers?.["x-forwarded-groups"]; //May be used to match with DepartmentId
    const models = req.app.get('db_model');

    if (!req.user) {
        let user = await models.User.find_by_email(oidcEmail);
        if (user === null) {
            const newUser = {
                email: oidcEmail,
                lastname: oidcName,
                name: oidcName,
                companyId: 1, //TODO: move to config
                DepartmentId: 1, //TODO: map to oidcGroups
                password: "null", // it should be string, not null
                admin: false,
                auto_approve: false,
                end_date: null,
                start_date: new Date(),
            }
            await models.User.create(newUser);
            user = await models.User.find_by_email(oidcEmail);
        }
        req.user = await user.reload_with_session_details();
    }

    next();
};
  • Apply patch:
Index: app.js
===================================================================
diff --git a/app.js b/app.js
--- a/app.js	(revision e0ca678927862600b65d22e090be92f608373f7f)
+++ b/app.js	(date 1680870481183)
@@ -33,8 +33,6 @@
 app.use(cookieParser());
 app.use(express.static(path.join(__dirname, 'public')));
 
-
-
 // Setup authentication mechanism
 const passport = require('./lib/passport')();
 
@@ -44,6 +42,7 @@
 app.use(passport.initialize());
 app.use(passport.session());
 
+app.use( require('./lib/middleware/oidc_handler') );
 
 
 // Custom middlewares
version: '3.8'
services:
  app:
    build: .

  oauth2-proxy:
    image: bitnami/oauth2-proxy
    depends_on:
      - app
    ports:
      - '3000:3000'
    command:
      - oauth2-proxy
      - --upstream=http://app:3000/
      - --http-address=:3000
      - --scope=email read_api read_user openid profile
      - --cookie-secure=true
      - --provider=gitlab
      - --email-domain=*
      - --oidc-issuer-url=https://gitlab.example.com
      - --redirect-url=http://localhost:3000/oauth2/callback
       # generate cookie-secret according to https://oauth2-proxy.github.io/oauth2-proxy/docs/configuration/overview#generating-a-cookie-secret
      - --cookie-secret=change_me
      - --client-id=change_me_to_gitlab_app_id
      - --client-secret=change_me_to_gitlab_app_secret

  db:
    image: mysql:8
    command:
      - mysqld
      - --default-authentication-plugin=mysql_native_password
    environment:
      MYSQL_ROOT_PASSWORD: 'password'
      MYSQL_PASSWORD: 'password'
      MYSQL_USER: 'user'
      MYSQL_DATABASE: 'timeoff'
  • replace DB config in config/db.json, and start app
  • login directly to timeoff-management app and create first campaign
  • on http://localhost:3000 you should see oidc proxy login page

Hope this project will be supported again. Big thanks to authors.

@pwpbarney
Copy link

@vsychov Thanks for sharing this. I'm trying to get it working with azure ad but I'm running into a problem with oidc_handler.js.

I'd really appreciate it if you could look at the below and point to what I'm doing wrong.

I'm not sure if the dependencies have changed since you originally did the design but I had to edit the Dockerfile to make it build:

FROM node:18-alpine

EXPOSE 3000

LABEL org.label-schema.schema-version="1.3.0"
LABEL org.label-schema.docker.cmd="docker run -d -p 3000:3000 --name timeoff-management"

RUN apk update
RUN apk upgrade
#Install dependencies
RUN apk add \
    git \
    make \
    python3 \
    g++ \
    gcc \
    libc-dev \
    clang 

#Add user so it doesn't run as root
RUN adduser --system app --home /app
USER app
WORKDIR /app

#clone app
RUN git clone https://github.com/timeoff-management/application.git timeoff-management

WORKDIR /app/timeoff-management

#Add in OIDC integration
COPY oidc_handler.js /app/timeoff-management/lib/middleware/oidc_handler.js
COPY app.js /app/timeoff-management/

RUN rm package-lock.json

#Update some dependencies
RUN sed -i 's/formidable"\: "~1.0.17/formidable"\: "1.1.1/' package.json
RUN sed -i 's/sqlite3"\: "^4.0.1/sqlite3"\: "^5.1.5/' package.json
RUN sed -i 's/node-sass"\: "^4.5.3/node-sass"\: "^7.0.3/' package.json
RUN sed -i 's/graceful-fs"\: "^4.4.2/graceful-fs"\: "4.4.2/' package.json

#install app
RUN npm install -y

CMD npm start

For simplicities sake I'm sticking with SQLite so haven't applied the MySQL patch.

The container runs but when you access the login page (original inbuilt one) it doesn't load and crashes the container. The logs show the following:

/app/timeoff-management/lib/middleware/oidc_handler.js:4
    const oidcEmail = req?.headers?.["x-forwarded-email"].toLowerCase();
                                                         ^

TypeError: Cannot read properties of undefined (reading 'toLowerCase')
    at module.exports (/app/timeoff-management/lib/middleware/oidc_handler.js:4:58)
    at Layer.handle [as handle_request] (/app/timeoff-management/node_modules/express/lib/router/layer.js:95:5)
    at trim_prefix (/app/timeoff-management/node_modules/express/lib/router/index.js:328:13)
    at /app/timeoff-management/node_modules/express/lib/router/index.js:286:9
    at Function.process_params (/app/timeoff-management/node_modules/express/lib/router/index.js:346:12)
    at next (/app/timeoff-management/node_modules/express/lib/router/index.js:280:10)
    at strategy.pass (/app/timeoff-management/node_modules/passport/lib/middleware/authenticate.js:325:9)
    at SessionStrategy.authenticate (/app/timeoff-management/node_modules/passport/lib/strategies/session.js:71:10)
    at attempt (/app/timeoff-management/node_modules/passport/lib/middleware/authenticate.js:348:16)
    at authenticate (/app/timeoff-management/node_modules/passport/lib/middleware/authenticate.js:349:7)
    at Layer.handle [as handle_request] (/app/timeoff-management/node_modules/express/lib/router/layer.js:95:5)
    at trim_prefix (/app/timeoff-management/node_modules/express/lib/router/index.js:328:13)
    at /app/timeoff-management/node_modules/express/lib/router/index.js:286:9
    at Function.process_params (/app/timeoff-management/node_modules/express/lib/router/index.js:346:12)
    at next (/app/timeoff-management/node_modules/express/lib/router/index.js:280:10)
    at initialize (/app/timeoff-management/node_modules/passport/lib/middleware/initialize.js:53:5)
    at Layer.handle [as handle_request] (/app/timeoff-management/node_modules/express/lib/router/layer.js:95:5)
    at trim_prefix (/app/timeoff-management/node_modules/express/lib/router/index.js:328:13)
    at /app/timeoff-management/node_modules/express/lib/router/index.js:286:9
    at Function.process_params (/app/timeoff-management/node_modules/express/lib/router/index.js:346:12)
    at next (/app/timeoff-management/node_modules/express/lib/router/index.js:280:10)
    at Model.<anonymous> (/app/timeoff-management/node_modules/express-session/index.js:506:7)
    at Model.tryCatcher (/app/timeoff-management/node_modules/sequelize/node_modules/bluebird/js/release/util.js:16:23)
    at Promise.successAdapter [as _fulfillmentHandler0] (/app/timeoff-management/node_modules/sequelize/node_modules/bluebird/js/release/nodeify.js:23:30)
    at Promise._settlePromise (/app/timeoff-management/node_modules/sequelize/node_modules/bluebird/js/release/promise.js:601:21)
    at Promise._settlePromise0 (/app/timeoff-management/node_modules/sequelize/node_modules/bluebird/js/release/promise.js:649:10)
    at Promise._settlePromises (/app/timeoff-management/node_modules/sequelize/node_modules/bluebird/js/release/promise.js:729:18)
    at _drainQueueStep (/app/timeoff-management/node_modules/sequelize/node_modules/bluebird/js/release/async.js:93:12)

Node.js v18.19.0

If I take out .toLowerCase(); like so: const oidcEmail = req?.headers?.["x-forwarded-email"]; //.toLowerCase(); I get email cannot be null error as follows:

> [email protected] start
> node bin/wwww


node:internal/process/promises:288
            triggerUncaughtException(err, true /* fromPromise */);
            ^
Error [SequelizeValidationError]: notNull Violation: email cannot be null,
notNull Violation: name cannot be null,
notNull Violation: lastname cannot be null
    at /app/timeoff-management/node_modules/sequelize/lib/instance-validator.js:74:14
    at tryCatcher (/app/timeoff-management/node_modules/sequelize/node_modules/bluebird/js/release/util.js:16:23)
    at Promise._settlePromiseFromHandler (/app/timeoff-management/node_modules/sequelize/node_modules/bluebird/js/release/promise.js:547:31)
    at Promise._settlePromise (/app/timeoff-management/node_modules/sequelize/node_modules/bluebird/js/release/promise.js:604:18)
    at Promise._settlePromise0 (/app/timeoff-management/node_modules/sequelize/node_modules/bluebird/js/release/promise.js:649:10)
    at Promise._settlePromises (/app/timeoff-management/node_modules/sequelize/node_modules/bluebird/js/release/promise.js:729:18)
    at Promise._fulfill (/app/timeoff-management/node_modules/sequelize/node_modules/bluebird/js/release/promise.js:673:18)
    at PromiseArray._resolve (/app/timeoff-management/node_modules/sequelize/node_modules/bluebird/js/release/promise_array.js:127:19)
    at PromiseArray._promiseFulfilled (/app/timeoff-management/node_modules/sequelize/node_modules/bluebird/js/release/promise_array.js:145:14)
    at Promise._settlePromise (/app/timeoff-management/node_modules/sequelize/node_modules/bluebird/js/release/promise.js:609:26)
    at Promise._settlePromise0 (/app/timeoff-management/node_modules/sequelize/node_modules/bluebird/js/release/promise.js:649:10)
    at Promise._settlePromises (/app/timeoff-management/node_modules/sequelize/node_modules/bluebird/js/release/promise.js:729:18)
    at _drainQueueStep (/app/timeoff-management/node_modules/sequelize/node_modules/bluebird/js/release/async.js:93:12)
    at _drainQueue (/app/timeoff-management/node_modules/sequelize/node_modules/bluebird/js/release/async.js:86:9)
    at Async._drainQueues (/app/timeoff-management/node_modules/sequelize/node_modules/bluebird/js/release/async.js:102:5)
    at Async.drainQueues [as _onImmediate] (/app/timeoff-management/node_modules/sequelize/node_modules/bluebird/js/release/async.js:15:14)
    at process.processImmediate (node:internal/timers:476:21) {
  errors: [
    {
      message: 'email cannot be null',
      type: 'notNull Violation',
      path: 'email',
      value: null
    },
    {
      message: 'name cannot be null',
      type: 'notNull Violation',
      path: 'name',
      value: null
    },
    {
      message: 'lastname cannot be null',
      type: 'notNull Violation',
      path: 'lastname',
      value: null
    }
  ]
}

Node.js v18.19.0

I'm not really sure where to go from here.

@vsychov
Copy link
Author

vsychov commented Dec 7, 2023

@pwpbarney, it's simple, it seems you don't have the x-forwarded-email header, which should always be included, https://github.com/oauth2-proxy/oauth2-proxy/blob/5e30a6fe948fc470108d9e522fe00ea656c94785/docs/docs/configuration/overview.md?plain=1#L145

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants