Skip to content

Commit

Permalink
Merge branch 'dev' into staging
Browse files Browse the repository at this point in the history
  • Loading branch information
krischarbonneau committed May 3, 2024
2 parents b9b47b8 + 0669c51 commit e00dead
Show file tree
Hide file tree
Showing 16 changed files with 199 additions and 39 deletions.
7 changes: 7 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Debug MSCA-D frontend",
"command": "npm run dev",
"request": "launch",
"type": "node-terminal",
"cwd": "${workspaceFolder}"
},
{
"type": "pwa-chrome",
"request": "launch",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ extraEnv:
value: {{ env "AUTH_ECAS_GLOBAL_LOGOUT_URL" }}
- name: AUTH_ECAS_REFRESH_ENDPOINT
value: {{ env "AUTH_ECAS_REFRESH_ENDPOINT" }}
- name: MSCA_NG_USER_ENDPOINT
value: {{ env "MSCA_NG_USER_ENDPOINT" }}
- name: AUTH_PRIVATE
valueFrom:
secretKeyRef:
Expand All @@ -69,6 +71,11 @@ extraEnv:
secretKeyRef:
name: secure-client-hub-{{ requiredEnv "BRANCH" | lower }}
key: NEXTAUTH_SECRET
- name: MSCA_NG_CREDS
valueFrom:
secretKeyRef:
name: secure-client-hub-{{ requiredEnv "BRANCH" | lower }}
key: MSCA_NG_CREDS
{{- if env "OTEL_API_KEY" }}
- name: OTEL_API_KEY
valueFrom:
Expand Down Expand Up @@ -162,6 +169,7 @@ secrets:
HOSTALIAS_IP: "{{ env "HOSTALIAS_IP" }}"
HOSTALIAS_HOSTNAME: {{ env "HOSTALIAS_HOSTNAME" }}
HOSTALIAS_CERT: {{ env "HOSTALIAS_CERT" }}
MSCA_NG_CREDS: {{ env "MSCA_NG_CREDS" }}


msca-d-frontend-auth-{{ requiredEnv "BRANCH" | lower }}:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ extraEnv:
- name: AUTH_ECAS_GLOBAL_LOGOUT_URL
value: {{ env "AUTH_ECAS_GLOBAL_LOGOUT_URL" }}
- name: AUTH_ECAS_REFRESH_ENDPOINT
value: {{ env "AUTH_ECAS_REFRESH_ENDPOINT" }}
value: {{ env "AUTH_ECAS_REFRESH_ENDPOINT" }}
- name: MSCA_NG_USER_ENDPOINT
value: {{ env "MSCA_NG_USER_ENDPOINT" }}
- name: AUTH_PRIVATE
valueFrom:
secretKeyRef:
Expand All @@ -69,6 +71,21 @@ extraEnv:
secretKeyRef:
name: secure-client-hub-{{ requiredEnv "BRANCH" | lower }}
key: NEXTAUTH_SECRET
- name: MSCA_NG_CREDS
valueFrom:
secretKeyRef:
name: secure-client-hub-{{ requiredEnv "BRANCH" | lower }}
key: MSCA_NG_CREDS
- name: HOSTALIAS_IP
valueFrom:
secretKeyRef:
name: secure-client-hub-{{ requiredEnv "BRANCH" | lower }}
key: HOSTALIAS_IP
- name: HOSTALIAS_HOSTNAME
valueFrom:
secretKeyRef:
name: secure-client-hub-{{ requiredEnv "BRANCH" | lower }}
key: HOSTALIAS_HOSTNAME
{{- if env "OTEL_API_KEY" }}
- name: OTEL_API_KEY
valueFrom:
Expand Down Expand Up @@ -160,6 +177,10 @@ secrets:
AUTH_PRIVATE: '{{ env "AUTH_PRIVATE" }}'
CLIENT_SECRET: {{ env "CLIENT_SECRET" }}
NEXTAUTH_SECRET: {{ env "NEXTAUTH_SECRET" }}
HOSTALIAS_IP: "{{ env "HOSTALIAS_IP" }}"
HOSTALIAS_HOSTNAME: {{ env "HOSTALIAS_HOSTNAME" }}
HOSTALIAS_CERT: {{ env "HOSTALIAS_CERT" }}
MSCA_NG_CREDS: {{ env "MSCA_NG_CREDS" }}
autoscaling:
enabled: true
name: service-canada-client-hub
Expand Down
11 changes: 11 additions & 0 deletions lib/auth.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { getToken } from 'next-auth/jwt'
import axios from 'axios'
import { getLogger } from '../logging/log-util'
import * as jose from 'jose'

const logger = getLogger('auth-helpers')

Expand All @@ -19,6 +20,16 @@ export async function AuthIsValid(req, session) {
return false
}

//This function grabs the idToken from the getToken function and decodes it
export async function getIdToken(req) {
const token = await getToken({ req })
if (token) {
const idToken = jose.decodeJwt(token.id_token)
return idToken
}
return token
}

export async function ValidateSession(clientId, sharedSessionId) {
logger.trace('Renewing session...')

Expand Down
23 changes: 20 additions & 3 deletions package-lock.json

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

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"babel-plugin-macros": "^3.1.0",
"cachified": "^3.5.4",
"cross-env": "^7.0.3",
"jose": "^5.2.4",
"lodash.throttle": "^4.1.1",
"lru-cache": "^10.2.0",
"markdown-to-jsx": "^7.4.5",
Expand Down
80 changes: 74 additions & 6 deletions pages/api/auth/[...nextauth].ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,28 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import NextAuth from 'next-auth'
import type { NextAuthOptions } from 'next-auth'

import { getLogger } from '../../../logging/log-util'
import axios from 'axios'
import * as jose from 'jose'

//The below sets the minimum logging level to error and surpresses everything below that
const logger = getLogger('next-auth')
logger.level = 'warn'

/*Adds the signing algorithm to the end of the private key. Necessary to decrypt the userinfo_token.
* Simply adding this to the end of the environment variable breaks the idToken decryption so this is a workaround.
* May need to look at regenerating the keyset at a later date.*/
const jwk = JSON.parse(process.env.AUTH_PRIVATE ?? '{}')
jwk.alg = 'RS256'

async function decryptJwe(jwe: string, jwk: any) {
const key = await jose.importJWK({ ...jwk })
const decryptResult = await jose.compactDecrypt(jwe, key, {
keyManagementAlgorithms: ['RSA-OAEP-256'],
})
return jose.decodeJwt(decryptResult.plaintext.toString())
}

export const authOptions: NextAuthOptions = {
providers: [
{
Expand Down Expand Up @@ -38,12 +54,61 @@ export const authOptions: NextAuthOptions = {
jwks: {
keys: [JSON.parse(process.env.AUTH_PRIVATE ?? '{}')],
},
userinfo: process.env.AUTH_ECAS_USERINFO,
userinfo: {
async request(context) {
//Necessary to test locally until we no longer need the proxy. Will use request without proxy on deployed app
const res =
process.env.AUTH_ON_PROXY &&
process.env.AUTH_ON_PROXY.toLowerCase() === 'true'
? await axios
.get(process.env.AUTH_ECAS_USERINFO as string, {
headers: {
Authorization: `Bearer ${context.tokens.access_token}`,
},
proxy: {
protocol: 'http',
host: 'localhost',
port: 3128,
},
})
.then((response) => response)
.catch((error) => logger.error(error))
: await axios
.get(process.env.AUTH_ECAS_USERINFO as string, {
headers: {
Authorization: `Bearer ${context.tokens.access_token}`,
},
})
.then((response) => response)
.catch((error) => logger.error(error))
return res?.data
},
},
idToken: true,
checks: ['state', 'nonce'],
profile(profile) {
profile: async (profile) => {
profile = await decryptJwe(profile.userinfo_token, jwk)
//Make call to msca-ng API to update last login date
const response = await axios
.post(
`${process.env.HOSTALIAS_HOSTNAME}${process.env.MSCA_NG_USER_ENDPOINT}`,
{
pid: profile.sin,
spid: profile.uid,
},
{
headers: {
'authorization': `Basic ${process.env.MSCA_NG_CREDS}`,
'Content-Type': 'application/json',
},
},
)
.then((response) => response)
.catch((error) => logger.error(error))
console.log(response)
return {
id: profile.sid,
id: profile.sub,
...profile,
}
},
},
Expand All @@ -57,8 +122,8 @@ export const authOptions: NextAuthOptions = {
maxAge: 5 * 10 * 24, //20 minutes
},
callbacks: {
async jwt({ token, account }) {
return { ...token, ...account }
async jwt({ token, user, account }) {
return { ...token, ...user, ...account }
},
async redirect({ url, baseUrl }) {
// Allows relative callback URLs
Expand All @@ -68,6 +133,9 @@ export const authOptions: NextAuthOptions = {
//else if (process.env.AUTH_ECAS_GLOBAL_LOGOUT_URL === url) return url
return baseUrl
},
async session({ session }) {
return { ...session }
},
},
logger: {
error(code, metadata) {
Expand Down
12 changes: 8 additions & 4 deletions pages/api/refresh-msca.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@
*
*/

import { AuthIsDisabled, AuthIsValid, ValidateSession } from '../../lib/auth'
import {
AuthIsDisabled,
AuthIsValid,
ValidateSession,
getIdToken,
} from '../../lib/auth'
import { getLogger } from '../../logging/log-util'
import { authOptions } from '../../pages/api/auth/[...nextauth]'
import { getServerSession } from 'next-auth/next'
import { getToken } from 'next-auth/jwt'

// Including crypto module
const crypto = require('crypto')
Expand All @@ -18,7 +22,7 @@ logger.level = 'error'

export default async function handler(req, res) {
const session = await getServerSession(req, res, authOptions)
const token = await getToken({ req })
const token = await getIdToken(req)
//Generate a random id for each request to ensure unique responses/no caching
const id = crypto.randomBytes(20).toString('hex')

Expand All @@ -31,7 +35,7 @@ export default async function handler(req, res) {
//If auth session is valid, make GET request to validateSession endpoint
const sessionValid = await ValidateSession(
process.env.CLIENT_ID,
token.sub,
token.sid,
)
if (sessionValid) {
res.status(200).json({ success: sessionValid, id: id })
Expand Down
12 changes: 8 additions & 4 deletions pages/auth/login.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@ import LoadingSpinner from '../../components/LoadingSpinner'
import MetaData from '../../components/MetaData'
import { authOptions } from '../../pages/api/auth/[...nextauth]'
import { getServerSession } from 'next-auth/next'
import { getToken } from 'next-auth/jwt'
import { AuthIsDisabled, AuthIsValid, ValidateSession } from '../../lib/auth'
import {
AuthIsDisabled,
AuthIsValid,
ValidateSession,
getIdToken,
} from '../../lib/auth'

export default function Login(props) {
const router = useRouter()
Expand Down Expand Up @@ -57,13 +61,13 @@ export async function getServerSideProps({ req, res, locale }) {
const authDisabled = AuthIsDisabled() ? true : false

const session = await getServerSession(req, res, authOptions)
const token = await getToken({ req })
const token = await getIdToken(req)

//If Next-Auth session is valid, check to see if ECAS session is and then redirect to dashboard instead of reinitiating auth
if (!AuthIsDisabled() && (await AuthIsValid(req, session))) {
const sessionValid = await ValidateSession(
process.env.CLIENT_ID,
token?.sub,
token?.sid,
)
if (sessionValid) {
return {
Expand Down
7 changes: 4 additions & 3 deletions pages/contact-us/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ import {
AuthIsValid,
ValidateSession,
Redirect,
getIdToken,
} from '../../lib/auth'
import { authOptions } from '../api/auth/[...nextauth]'
import { getServerSession } from 'next-auth/next'
import { GetServerSideProps } from 'next'
import { getToken } from 'next-auth/jwt'

interface Data {
title: string
Expand Down Expand Up @@ -89,16 +89,17 @@ const ContactUsPage = (props: ContactUsPageProps) => {

export const getServerSideProps = (async ({ req, res, locale, params }) => {
const session = await getServerSession(req, res, authOptions)
const token = await getToken({ req })

if (!AuthIsDisabled() && !(await AuthIsValid(req, session)))
return Redirect(locale)

const token = await getIdToken(req)

//If Next-Auth session is valid, check to see if ECAS session is and redirect to logout if not
if (!AuthIsDisabled() && (await AuthIsValid(req, session))) {
const sessionValid = await ValidateSession(
process.env.CLIENT_ID,
token?.sub,
token?.sid,
)
if (!sessionValid) {
return {
Expand Down
Loading

0 comments on commit e00dead

Please sign in to comment.