Skip to content

Commit

Permalink
Add support for App ID authentication
Browse files Browse the repository at this point in the history
- Add express server implementation integrated with App ID
- Add Dockerfile-express that builds container image using express server and create Dockerfile soft link to -express file
- Fix nginx proxy config (need to configure App ID integration)

closes #72

Signed-off-by: Sean Sundberg <[email protected]>
  • Loading branch information
seansund committed Oct 5, 2023
1 parent 28461d7 commit 5cd72be
Show file tree
Hide file tree
Showing 18 changed files with 3,370 additions and 50 deletions.
13 changes: 0 additions & 13 deletions Dockerfile

This file was deleted.

1 change: 1 addition & 0 deletions Dockerfile
13 changes: 13 additions & 0 deletions Dockerfile-dev
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
FROM registry.access.redhat.com/ubi9/nodejs-18:1-70.1695740477

WORKDIR /opt/app-root/src

COPY --chown=default:root . .

RUN mkdir -p /opt/app-root/src/node_modules && \
ls -lA && \
npm ci

EXPOSE 5173

CMD ["npm", "run", "dev", "--", "--host"]
26 changes: 26 additions & 0 deletions Dockerfile-express
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
FROM registry.access.redhat.com/ubi9/nodejs-18:1-70.1695740477 AS builder

WORKDIR /opt/app-root/src

COPY --chown=default:root . .

RUN mkdir -p /opt/app-root/src/node_modules && \
ls -lA && \
npm ci && \
npm run build

FROM registry.access.redhat.com/ubi9/nodejs-18:1-70.1695740477

WORKDIR /opt/app-root/src

COPY --from=builder --chown=default:root /opt/app-root/src/dist ./dist

WORKDIR /opt/app-root/src/server

COPY --chown=default:root ./server/* .

RUN npm ci

EXPOSE 8080

CMD ["npm", "start", "--", "--host"]
4 changes: 2 additions & 2 deletions Dockerfile-prod → Dockerfile-nginx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM registry.access.redhat.com/ubi9/nodejs-18:1-62.1692771036 AS builder
FROM registry.access.redhat.com/ubi9/nodejs-18:1-70.1695740477 AS builder

WORKDIR /opt/app-root/src

Expand All @@ -9,7 +9,7 @@ RUN mkdir -p /opt/app-root/src/node_modules && \
npm ci && \
npm run build

FROM registry.access.redhat.com/ubi9/nginx-122:1-22.1692771040
FROM registry.access.redhat.com/ubi9/nginx-122:1-31

WORKDIR /opt/app-root/src

Expand Down
3 changes: 3 additions & 0 deletions config/nginx/nginx-base.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
location / {
try_files $uri /index.html;
}
9 changes: 9 additions & 0 deletions config/nginx/nginx-proxy.conf
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@

location /api {
proxy_ssl_name watsonx-demo-svc.16suy1d71dxc.us-south.codeengine.appdomain.cloud;
proxy_ssl_server_name on;
proxy_http_version 1.1;
proxy_pass https://watsonx-demo-svc.16suy1d71dxc.us-south.codeengine.appdomain.cloud/;
}

location /graphql {
proxy_ssl_name watsonx-demo-svc.16suy1d71dxc.us-south.codeengine.appdomain.cloud;
proxy_ssl_server_name on;
proxy_http_version 1.1;
proxy_pass https://watsonx-demo-svc.16suy1d71dxc.us-south.codeengine.appdomain.cloud/graphql;
}

location /subscription {
proxy_ssl_name watsonx-demo-svc.16suy1d71dxc.us-south.codeengine.appdomain.cloud;
proxy_ssl_server_name on;
proxy_pass https://watsonx-demo-svc.16suy1d71dxc.us-south.codeengine.appdomain.cloud/subscription;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
Expand Down
39 changes: 36 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
"@ibm-watson/assistant-web-chat-react": "^1.0.4",
"dayjs": "^1.11.10",
"graphql-ws": "^5.14.1",
"ibmcloud-appid-js": "^1.0.1",
"jotai": "^2.4.3",
"js-cookie": "^3.0.5",
"optional-js": "^2.3.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
Expand All @@ -29,6 +31,7 @@
},
"devDependencies": {
"@types/carbon__icons-react": "^11.26.1",
"@types/js-cookie": "^3.0.4",
"@types/node": "^20.7.1",
"@types/react": "^18.2.23",
"@types/react-dom": "^18.2.8",
Expand Down
182 changes: 182 additions & 0 deletions server/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@

const express = require('express');
const session = require('express-session');
const proxy = require('express-http-proxy');
const url = require('url');
const path = require('path');
const passport = require('passport');
const WebAppStrategy = require("ibmcloud-appid").WebAppStrategy;

const CALLBACK_URL = "/ibm/cloud/appid/callback";
const LOGOUT_URL = "/ibm/cloud/appid/logout";
const LOGOUT2_URL = "/logout";
const LOGIN_URL = "/ibm/cloud/appid/login";
const LOGIN2_URL = "/login";
const LANDING_PAGE_URL = "/";
const CHANGE_PASSWORD_URL = "/ibm/cloud/appid/change_password";
const CHANGE_PASSWORD2_URL = "/change_password";
const FORGOT_PASSWORD_URL = "/ibm/cloud/appid/forgot_password";
const FORGOT_PASSWORD2_URL = "/forgot_password";

const BACKEND_HOST = process.env.BACKEND_HOST || 'localhost:3000'
const PORT = process.env.PORT || '8080'
const HOST = process.env.HOST || 'http://localhost:8080'

const COOKIE_NAME = 'refreshToken'

const createServer = () => {
const app = express();
app.use(session({
secret: "654321",
resave: true,
saveUninitialized: true
}));
app.use(passport.initialize());
app.use(passport.session());

const webAppStrategy = new WebAppStrategy({
clientId: "59ce4a7b-a417-461b-a491-14c647e22b6f",
tenantId: "cefd8632-e25b-4b75-9077-a8b6952d39f4",
secret: "NTMwODNlM2EtOGYyOC00ZTEyLWIwNmYtNWNjMGY4ZjJmNTUx",
oAuthServerUrl: "https://us-south.appid.cloud.ibm.com/oauth/v4/cefd8632-e25b-4b75-9077-a8b6952d39f4",
redirectUri: `${HOST}${CALLBACK_URL}`,
})

passport.use(webAppStrategy);

passport.serializeUser((user, cb) => {
cb(null, user);
});
passport.deserializeUser((obj, cb) => {
cb(null, obj);
});

app.get(CALLBACK_URL, passport.authenticate(WebAppStrategy.STRATEGY_NAME, { keepSessionInfo: true }));

app.get(LOGIN_URL, passport.authenticate(WebAppStrategy.STRATEGY_NAME, {
successRedirect: LANDING_PAGE_URL,
forceLogin: true
}));
app.get(LOGIN2_URL, passport.authenticate(WebAppStrategy.STRATEGY_NAME, {
successRedirect: LANDING_PAGE_URL,
forceLogin: true
}));

app.get(FORGOT_PASSWORD_URL, passport.authenticate(WebAppStrategy.STRATEGY_NAME, {
successRedirect: LANDING_PAGE_URL,
show: WebAppStrategy.FORGOT_PASSWORD
}));
app.get(FORGOT_PASSWORD2_URL, passport.authenticate(WebAppStrategy.STRATEGY_NAME, {
successRedirect: LANDING_PAGE_URL,
show: WebAppStrategy.FORGOT_PASSWORD
}));

app.get(CHANGE_PASSWORD_URL, passport.authenticate(WebAppStrategy.STRATEGY_NAME, {
successRedirect: LANDING_PAGE_URL,
show: WebAppStrategy.CHANGE_PASSWORD
}));
app.get(CHANGE_PASSWORD2_URL, passport.authenticate(WebAppStrategy.STRATEGY_NAME, {
successRedirect: LANDING_PAGE_URL,
show: WebAppStrategy.CHANGE_PASSWORD
}));

const logout = (req, res) => {
console.log('Logging out')
req._sessionManager = false;
WebAppStrategy.logout(req);
req.logout();
req.session = null;
res.clearCookie(COOKIE_NAME);
res.redirect(LANDING_PAGE_URL);
}
app.get(LOGOUT_URL, logout);
app.get(LOGOUT2_URL, logout);

const apiProxy = proxy(BACKEND_HOST, {
proxyReqPathResolver: req => url.parse(req.baseUrl).path
});
app.use('/api/*', apiProxy);

const graphqlProxy = proxy(`${BACKEND_HOST}/graphql`, {
proxyReqPathResolver: req => url.parse(req.baseUrl).path
});
app.use('/graphql/*', graphqlProxy);
app.use('/graphql', graphqlProxy);

const subscriptionProxy = proxy(`${BACKEND_HOST}/subscription`, {
proxyReqPathResolver: req => url.parse(req.baseUrl).path
});
app.use('/subscription/*', subscriptionProxy);
app.use('/subscription', subscriptionProxy);

function storeRefreshTokenInCookie(req, res, next) {
const refreshToken = req.session[WebAppStrategy.AUTH_CONTEXT][COOKIE_NAME];
console.log('Storing refresh token: ', {refreshToken})
if (refreshToken) {
/* An example of storing user's refresh-token in a cookie with expiration of a month */
res.cookie(COOKIE_NAME, refreshToken, {
maxAge: 1000 * 60 * 60 * 24 * 30 /* 30 days */
});
}
next();
}

function isLoggedIn(req) {
const result = req.session[WebAppStrategy.AUTH_CONTEXT];

console.log('isLoggedIn? ', result)

return result;
}

function tryToRefreshTokenIfNotLoggedIn(req, res, next) {
if (isLoggedIn(req)) {
return next();
}

const refreshToken = (req.cookies || {})[COOKIE_NAME]
if (!refreshToken) {
return next();
}

console.log('Refreshing tokens')
webAppStrategy.refreshTokens(req, refreshToken).then(function () {
console.log('Next')
next();
}).catch(err => console.error('Error refreshing tokens: ', {err}));
}

app.get(
"/secure/*",
tryToRefreshTokenIfNotLoggedIn,
passport.authenticate(WebAppStrategy.STRATEGY_NAME, { keepSessionInfo: true }),
storeRefreshTokenInCookie,
(req, res) => {
res.sendFile(path.resolve(__dirname, '..', 'dist', 'index.html'))
}
);

//
// app.get(
// "/secure/*",
// passport.authenticate(WebAppStrategy.STRATEGY_NAME),
// (req, res) => {
// res.sendFile(path.resolve(__dirname, '..', 'dist', 'index.html'))
// }
// );

// app.get('*', passport.authenticate(WebAppStrategy.STRATEGY_NAME), (req, res) => {
// res.sendFile(path.resolve(__dirname, '..', 'dist', 'index.html'))
// })
// app.get('*', (req, res) => {
// res.sendFile(path.resolve(__dirname, '..', 'dist', 'index.html'))
// })
app.use(express.static(path.join(__dirname, '..', 'dist')))


return app;
}

createServer().listen(PORT, () => {
console.log(`Server started on port ${PORT}`)
})
Loading

0 comments on commit 5cd72be

Please sign in to comment.