From aa1822ca864243b7a5e9d1c89ba44a4c3c5e059c Mon Sep 17 00:00:00 2001 From: Theodor Kvalsvik Lauritzen <19690242+theodorklauritzen@users.noreply.github.com> Date: Thu, 7 Nov 2024 19:44:37 +0100 Subject: [PATCH 1/8] feat: Add compression to omegaID --- docs | 2 +- .../identification/OmegaIdElement.module.scss | 6 +- .../OmegaId/identification/OmegaIdElement.tsx | 19 +-- .../OmegaId/reader/OmegaIdReader.tsx | 15 ++- src/app/users/[username]/page.module.scss | 4 +- src/app/users/[username]/page.tsx | 6 +- src/jwt/jwt.ts | 2 +- src/services/omegaid/ConfigVars.ts | 9 -- src/services/omegaid/Types.ts | 8 +- src/services/omegaid/compress.ts | 110 ++++++++++++++++++ src/services/omegaid/generate.ts | 7 +- 11 files changed, 140 insertions(+), 48 deletions(-) create mode 100644 src/services/omegaid/compress.ts diff --git a/docs b/docs index 97b8ef607..3cc94e532 160000 --- a/docs +++ b/docs @@ -1 +1 @@ -Subproject commit 97b8ef607aa78cc38d04c030fabd8d55806cd1df +Subproject commit 3cc94e532274ee194ac05edb33b76980e415b3dd diff --git a/src/app/_components/OmegaId/identification/OmegaIdElement.module.scss b/src/app/_components/OmegaId/identification/OmegaIdElement.module.scss index 9f94b52d5..811707e85 100644 --- a/src/app/_components/OmegaId/identification/OmegaIdElement.module.scss +++ b/src/app/_components/OmegaId/identification/OmegaIdElement.module.scss @@ -2,8 +2,4 @@ .OmegaIdElement { max-width: 400px; - > p { - width: 100%; - text-align: center; - } -} \ No newline at end of file +} diff --git a/src/app/_components/OmegaId/identification/OmegaIdElement.tsx b/src/app/_components/OmegaId/identification/OmegaIdElement.tsx index 7ca89981c..4f3098c14 100644 --- a/src/app/_components/OmegaId/identification/OmegaIdElement.tsx +++ b/src/app/_components/OmegaId/identification/OmegaIdElement.tsx @@ -2,27 +2,20 @@ import styles from './OmegaIdElement.module.scss' import { generateOmegaIdAction } from '@/actions/omegaid/generate' import { readJWTPayload } from '@/jwt/jwtReadUnsecure' +import { compressOmegaId } from '@/services/omegaid/compress' import { useQRCode } from 'next-qrcode' import { useEffect, useState } from 'react' const EXPIRY_THRESHOLD = 60 -type PropTypes = { +export default function OmegaIdElement({ token }: { token: string, -} - -export default function OmegaIdElement({ token }: PropTypes) { +}) { const [tokenState, setTokenState] = useState(token) const { SVG } = useQRCode() - const JWTPayload = readJWTPayload<{ - gn?: string, - sn?: string, - }>(token) - - const firstname = JWTPayload.gn ?? '' - const lastname = JWTPayload.sn ?? '' + const JWTPayload = readJWTPayload(tokenState) const [expiryTime, setExpiryTime] = useState(new Date((JWTPayload.exp - EXPIRY_THRESHOLD) * 1000)) @@ -48,9 +41,7 @@ export default function OmegaIdElement({ token }: PropTypes) { return
- -

{firstname} {lastname}

} diff --git a/src/app/_components/OmegaId/reader/OmegaIdReader.tsx b/src/app/_components/OmegaId/reader/OmegaIdReader.tsx index 70e0ce1fd..14fd9998d 100644 --- a/src/app/_components/OmegaId/reader/OmegaIdReader.tsx +++ b/src/app/_components/OmegaId/reader/OmegaIdReader.tsx @@ -6,6 +6,7 @@ import { Html5QrcodeScanner } from 'html5-qrcode' import { useEffect, useState } from 'react' import { v4 as uuid } from 'uuid' import type { OmegaId } from '@/services/omegaid/Types' +import { decomporessOmegaId as decompressOmegaId } from '@/services/omegaid/compress' /** * Renders a component for reading OmegaId QR codes. @@ -51,11 +52,17 @@ export default function OmegaIdReader({ let lastReadTime = 0 let lastReadUserId = -1 - html5QrcodeScanner.render(async (token) => { - const parse = await parseJWT(token, publicKey, expiryOffset ?? 100) + html5QrcodeScanner.render(async (rawToken) => { + const token = decompressOmegaId(rawToken) + if (!token.success) { + setFeedBack({ + status: 'ERROR', + text: 'Ugyldig QR kode' + }) + return + } + const parse = await parseJWT(token.data, publicKey, expiryOffset ?? 100, 'omegaid') if (!parse.success) { - console.log(parse) - const msg = parse.error?.map(e => e.message).join(' / ') ?? 'Ukjent feil' setFeedBack({ diff --git a/src/app/users/[username]/page.module.scss b/src/app/users/[username]/page.module.scss index 69bae29a8..966cc0b7f 100644 --- a/src/app/users/[username]/page.module.scss +++ b/src/app/users/[username]/page.module.scss @@ -66,8 +66,8 @@ } .omegaId { - min-width: 50vw; - min-height: 50vw; + min-width: min(50vw, 400px); + min-height: min(50vw, 400px); display: grid; place-items: center; > * { diff --git a/src/app/users/[username]/page.tsx b/src/app/users/[username]/page.tsx index 8cddfcc63..0e93cfc03 100644 --- a/src/app/users/[username]/page.tsx +++ b/src/app/users/[username]/page.tsx @@ -47,6 +47,8 @@ export default async function User({ params }: PropTypes) { { username: profile.user.username } ).auth(session) + const showOmegaId = session.user?.username === params.username + return (
@@ -57,7 +59,7 @@ export default async function User({ params }: PropTypes) {

{`${profile.user.firstname} ${profile.user.lastname}`}

- @@ -67,7 +69,7 @@ export default async function User({ params }: PropTypes) {
-
+ }
{ studyProgramme && ( diff --git a/src/jwt/jwt.ts b/src/jwt/jwt.ts index beee35138..d9eec9036 100644 --- a/src/jwt/jwt.ts +++ b/src/jwt/jwt.ts @@ -29,7 +29,7 @@ export function generateJWT( throw new ServerError('INVALID CONFIGURATION', 'Missing secret for JWT generation') } - return sign(payload, asymetric ? process.env.JWT_PRIVATE_KEY : process.env.NEXTAUTH_SECRET, { + return sign(payload, Buffer.from(asymetric ? process.env.JWT_PRIVATE_KEY : process.env.NEXTAUTH_SECRET), { audience: aud, algorithm: asymetric ? 'ES256' : 'HS256', issuer: JWT_ISSUER, diff --git a/src/services/omegaid/ConfigVars.ts b/src/services/omegaid/ConfigVars.ts index 712955b03..dfad0f692 100644 --- a/src/services/omegaid/ConfigVars.ts +++ b/src/services/omegaid/ConfigVars.ts @@ -1,11 +1,2 @@ -import type { UserFiltered } from '@/services/users/Types' - - -export const omegaIdFields = [ - 'id', - 'firstname', - 'lastname', - 'username', -] as const satisfies (keyof UserFiltered)[] export const OmegaIdExpiryTime = 60 * 5 // 5 minutes diff --git a/src/services/omegaid/Types.ts b/src/services/omegaid/Types.ts index 981fe9f8b..3d82cbe70 100644 --- a/src/services/omegaid/Types.ts +++ b/src/services/omegaid/Types.ts @@ -1,11 +1,9 @@ -import type { omegaIdFields } from './ConfigVars' import type { UserFiltered } from '@/services/users/Types' -export type OmegaId = Pick +export type OmegaId = Pick export type OmegaIdJWT = { + iat: number, + exp: number, sub: UserFiltered['id'], - usrnm: UserFiltered['username'], - gn: UserFiltered['firstname'], - sn: UserFiltered['lastname'], } diff --git a/src/services/omegaid/compress.ts b/src/services/omegaid/compress.ts new file mode 100644 index 000000000..6f32b56fd --- /dev/null +++ b/src/services/omegaid/compress.ts @@ -0,0 +1,110 @@ +import { JWT_ISSUER } from '@/auth/ConfigVars' +import type { ActionReturn, ActionReturnError } from '@/actions/Types' +import type { OmegaIdJWT } from '@/services/omegaid/Types' + +/** + * This file handles compression and decompression of omegaID + */ +export function compressOmegaId(token: string): string { + const parts = token.split('.') + const payloadCompressed = compressPayload(parts[1]) + const payload = base64ToBigInt(payloadCompressed) + const sign = base64ToBigInt(parts[2]) + const ret = `${payload}.${sign}` + return ret +} + +function decodeBase64Url(base64: string) { + return atob(base64.replaceAll('-', '+').replaceAll('_', '/')) +} + +function encodeBase64Url(data: string): string { + return btoa(data).replaceAll('+', '-').replaceAll('/', '_').replaceAll('=', '') +} + +function base64ToBigInt(base64: string): string { + const binaryString = decodeBase64Url(base64) + let bigint = BigInt(0) + + // Convert binary string to BigInt + for (let i = 0; i < binaryString.length; i++) { + bigint = (bigint << BigInt(8)) | BigInt(binaryString.charCodeAt(i)) + } + + return bigint.toString() +} + +function bigIntToBase64(bigIntString: string): string { + let bigInt = BigInt(bigIntString) + let binaryString = '' + + while (bigInt > BigInt(0)) { + const nextChar = bigInt & BigInt(0xFF) + binaryString += String.fromCharCode(Number(nextChar)) + bigInt >>= BigInt(8) + } + + binaryString = binaryString.split('').reverse().join('') + + return encodeBase64Url(binaryString) +} + +function compressPayload(payload: string): string { + const binaryString = decodeBase64Url(payload) + const payloadString = binaryString.toString() + const payloadJSON = JSON.parse(payloadString) as OmegaIdJWT + const shortPayloadString = `${payloadJSON.sub},${payloadJSON.iat},${payloadJSON.exp}` + return encodeBase64Url(shortPayloadString) +} + +function decompressPayload(rawdata: string): string { + const base64String = bigIntToBase64(rawdata) + const byteString = decodeBase64Url(base64String) + const dataString = byteString.toString().split(',') + const payload = { + sub: Number(dataString[0]), + iat: Number(dataString[1]), + exp: Number(dataString[2]), + aud: 'omegaid', + iss: JWT_ISSUER, + } + const payloadString = JSON.stringify(payload) + + return encodeBase64Url(payloadString) +} + +export function decomporessOmegaId(rawdata: string): ActionReturn { + const header = { + alg: 'ES256', + typ: 'JWT' + } + const headerJSONString = JSON.stringify(header) + const headerB64String = encodeBase64Url(headerJSONString) + + const errorReturn: ActionReturnError = { + success: false, + errorCode: 'JWT INVALID', + httpCode: 400, + error: [{ + message: 'QR code is not an OmegaId', + }], + } + + const rawDataSplit = rawdata.split('.') + if (rawDataSplit.length !== 2) { + return errorReturn + } + + try { + const payload = decompressPayload(rawDataSplit[0]) + + const signature = bigIntToBase64(rawDataSplit[1]) + + return { + success: true, + data: `${headerB64String}.${payload}.${signature}`, + } + } catch { + return errorReturn + } +} diff --git a/src/services/omegaid/generate.ts b/src/services/omegaid/generate.ts index b05f3b141..6a6377016 100644 --- a/src/services/omegaid/generate.ts +++ b/src/services/omegaid/generate.ts @@ -1,15 +1,12 @@ import 'server-only' import { OmegaIdExpiryTime } from './ConfigVars' import { generateJWT } from '@/jwt/jwt' -import type { OmegaId, OmegaIdJWT } from './Types' +import type { OmegaId } from './Types' export function generateOmegaId(user: OmegaId): string { - const payload: OmegaIdJWT = { + const payload = { sub: user.id, - usrnm: user.username, - gn: user.firstname, - sn: user.lastname, } return generateJWT('omegaid', payload, OmegaIdExpiryTime, true) From 39012ded3d6d1730faa71ebfe1f72491c6e192e9 Mon Sep 17 00:00:00 2001 From: Theodor Kvalsvik Lauritzen <19690242+theodorklauritzen@users.noreply.github.com> Date: Thu, 7 Nov 2024 20:04:42 +0100 Subject: [PATCH 2/8] fix: Admision trials --- src/actions/admission/create.ts | 5 +-- .../OmegaId/reader/OmegaIdReader.tsx | 6 ++-- .../admission/[admission]/registration.tsx | 14 ++++---- src/app/admin/admission/page.tsx | 2 +- src/jwt/parseJWTClient.ts | 34 ++++++++----------- src/services/admission/Types.ts | 7 ++++ src/services/admission/create.ts | 11 ++++-- 7 files changed, 43 insertions(+), 36 deletions(-) create mode 100644 src/services/admission/Types.ts diff --git a/src/actions/admission/create.ts b/src/actions/admission/create.ts index 8741c7b4f..fac7ef314 100644 --- a/src/actions/admission/create.ts +++ b/src/actions/admission/create.ts @@ -6,13 +6,14 @@ import { createAdmissionTrial } from '@/services/admission/create' import { createAdmissionTrialValidation } from '@/services/admission/validation' import { Session } from '@/auth/Session' import type { ActionReturn } from '@/actions/Types' -import type { Admission, AdmissionTrial } from '@prisma/client' +import type { Admission } from '@prisma/client' +import type { ExpandedAdmissionTrail } from '@/services/admission/Types' export async function createAdmissionTrialAction( admission: Admission, userId: FormData | number -): Promise> { +): Promise> { const session = await Session.fromNextAuth() const authRes = CreateAdmissionTrialAuther.dynamicFields({}).auth(session) diff --git a/src/app/_components/OmegaId/reader/OmegaIdReader.tsx b/src/app/_components/OmegaId/reader/OmegaIdReader.tsx index 14fd9998d..d1ad5fe22 100644 --- a/src/app/_components/OmegaId/reader/OmegaIdReader.tsx +++ b/src/app/_components/OmegaId/reader/OmegaIdReader.tsx @@ -27,7 +27,7 @@ export default function OmegaIdReader({ debounceThreshold, singleRead, }: { - successCallback: (user: OmegaId, token: string) => Promise<{ + successCallback: (user: number, token: string) => Promise<{ success: boolean, text: string, }>, @@ -72,7 +72,7 @@ export default function OmegaIdReader({ return } - const userId = parse.data.id + const userId = parse.data if (userId === lastReadUserId && Date.now() - lastReadTime < (debounceThreshold ?? 5000)) { lastReadTime = Date.now() @@ -84,7 +84,7 @@ export default function OmegaIdReader({ text: '...', }) - const results = await successCallback(parse.data, token) + const results = await successCallback(userId, token.data) if (results.success && (singleRead ?? false)) { html5QrcodeScanner.clear() diff --git a/src/app/admin/admission/[admission]/registration.tsx b/src/app/admin/admission/[admission]/registration.tsx index 021db5d85..c9b97c6c1 100644 --- a/src/app/admin/admission/[admission]/registration.tsx +++ b/src/app/admin/admission/[admission]/registration.tsx @@ -19,19 +19,17 @@ export default function RegisterAdmissiontrial({

Registrer med QR kode

{ - const results = await createAdmissionTrialAction(admission, user.id) + successCallback={async (userId) => { + const results = await createAdmissionTrialAction(admission, userId) let msg = results.success ? - `${user.firstname} er registrert` : + `${results.data.user.firstname} ${results.data.user.lastname} er registrert` : 'Kunne ikke regisrere bruker grunnet en ukjent feil.' if (!results.success && results.error) { - msg = `${user.firstname}: ${ - results.error - .map(e => e.message) - .reduce((acc, val) => `${acc}\n${val}`, '') - }` + msg = results.error + .map(e => e.message) + .reduce((acc, val) => `${acc}\n${val}`, '') } return { diff --git a/src/app/admin/admission/page.tsx b/src/app/admin/admission/page.tsx index 5c767ed32..8d98515bc 100644 --- a/src/app/admin/admission/page.tsx +++ b/src/app/admin/admission/page.tsx @@ -12,7 +12,7 @@ export default async function AdmissionTrials() {
    {AdmissionsArray.map(trial =>
  • - {AdmissionDisplayNames[trial]} + {AdmissionDisplayNames[trial]}
  • )}
diff --git a/src/jwt/parseJWTClient.ts b/src/jwt/parseJWTClient.ts index 74ac2bc12..dec03f0df 100644 --- a/src/jwt/parseJWTClient.ts +++ b/src/jwt/parseJWTClient.ts @@ -5,7 +5,6 @@ import { JWT_ISSUER } from '@/auth/ConfigVars' import { createActionError } from '@/actions/error' import type { OmegaJWTAudience } from '@/auth/Types' import type { ActionReturn } from '@/actions/Types' -import type { OmegaId } from '@/services/omegaid/Types' /** * Parses a JSON Web Token (JWT) and verifies its signature using the provided public key. @@ -16,11 +15,16 @@ import type { OmegaId } from '@/services/omegaid/Types' * @returns A promise that resolves to an `ActionReturn` object containing the parsed JWT payload if the JWT is valid, * or an error object if the JWT is invalid. */ -export async function parseJWT(token: string, publicKey: string, timeOffset: number): Promise> { +export async function parseJWT( + token: string, + publicKey: string, + timeOffset: number, + audience: OmegaJWTAudience +): Promise> { // TODO: This only works in safari and firefox :/// - function invalidJWT(message?: string): ActionReturn { - return createActionError('JWT INVALID', message || 'Ugyldig QR kode') + function invalidJWT(message?: string): ActionReturn { + return createActionError('JWT INVALID', message || 'Invalid JWT') } if (timeOffset < 0) { @@ -30,7 +34,7 @@ export async function parseJWT(token: string, publicKey: string, timeOffset: num const tokenS = token.split('.') if (tokenS.length !== 3) { - return invalidJWT('Ugyldig QR kode type') + return invalidJWT('Malformatted JWT') } const keyStripped = publicKey @@ -68,35 +72,25 @@ export async function parseJWT(token: string, publicKey: string, timeOffset: num try { const payload = readJWTPayload(token) - if (!( - typeof payload.usrnm === 'string' && - typeof payload.gn === 'string' && - typeof payload.sn === 'string' && - typeof payload.sub === 'number' - )) { - return invalidJWT('Invalid fields') + if (typeof payload.sub !== 'number') { + return invalidJWT('JWT is missing sub field') } if (new Date(payload.exp * 1000 + timeOffset) < new Date()) { - return invalidJWT('QR koden er utløpt') + return invalidJWT('JWT has expired') } if (payload.iss !== JWT_ISSUER) { return invalidJWT('Invalid issuer') } - if (payload.aud !== 'omegaid' satisfies OmegaJWTAudience) { + if (payload.aud !== audience) { return invalidJWT('Invalid audience') } return { success: true, - data: { - id: payload.sub, - username: payload.usrnm, - firstname: payload.gn, - lastname: payload.sn, - } + data: payload.sub } } catch { return invalidJWT('An unexpected error occured') diff --git a/src/services/admission/Types.ts b/src/services/admission/Types.ts new file mode 100644 index 000000000..097d1de8b --- /dev/null +++ b/src/services/admission/Types.ts @@ -0,0 +1,7 @@ +import type { AdmissionTrial } from '@prisma/client' +import type { UserFiltered } from '@/services/users/Types' + + +export type ExpandedAdmissionTrail = AdmissionTrial & { + user: UserFiltered +} diff --git a/src/services/admission/create.ts b/src/services/admission/create.ts index 58318f356..507a67ab8 100644 --- a/src/services/admission/create.ts +++ b/src/services/admission/create.ts @@ -4,12 +4,14 @@ import { readUserAdmissionTrials } from './read' import { prismaCall } from '@/services/prismaCall' import { updateUserOmegaMembershipGroup } from '@/services/groups/omegaMembershipGroups/update' import prisma from '@/prisma' -import { Admission, type AdmissionTrial } from '@prisma/client' +import { userFilterSelection } from '@/services/users/ConfigVars' +import { Admission } from '@prisma/client' import type { CreateAdmissionTrialType } from './validation' +import type { ExpandedAdmissionTrail } from './Types' export async function createAdmissionTrial( data: CreateAdmissionTrialType['Detailed'] -): Promise { +): Promise { const parse = createAdmissionTrialValidation.detailedValidate(data) const results = await prismaCall(() => prisma.admissionTrial.create({ @@ -25,6 +27,11 @@ export async function createAdmissionTrial( }, }, admission: parse.admission, + }, + include: { + user: { + select: userFilterSelection + } } })) From 1d212235a578938c5406dbdf38b11d548a024832 Mon Sep 17 00:00:00 2001 From: Theodor Kvalsvik Lauritzen <19690242+theodorklauritzen@users.noreply.github.com> Date: Thu, 7 Nov 2024 20:20:28 +0100 Subject: [PATCH 3/8] chore: Add study programme admin page to the admin side panel --- src/app/admin/SlideSidebar.tsx | 4 ++++ src/app/admin/{studyprogrammes => study-programmes}/page.tsx | 0 .../studyProgrammeTable.module.scss | 0 .../studyProgrammeTable.tsx | 0 .../updateStudyProgrammeForm.tsx | 0 5 files changed, 4 insertions(+) rename src/app/admin/{studyprogrammes => study-programmes}/page.tsx (100%) rename src/app/admin/{studyprogrammes => study-programmes}/studyProgrammeTable.module.scss (100%) rename src/app/admin/{studyprogrammes => study-programmes}/studyProgrammeTable.tsx (100%) rename src/app/admin/{studyprogrammes => study-programmes}/updateStudyProgrammeForm.tsx (100%) diff --git a/src/app/admin/SlideSidebar.tsx b/src/app/admin/SlideSidebar.tsx index 6aa2f7e4a..24beae2ed 100644 --- a/src/app/admin/SlideSidebar.tsx +++ b/src/app/admin/SlideSidebar.tsx @@ -92,6 +92,10 @@ const navigations = [ { title: 'Klasser', href: '/admin/classes' + }, + { + title: 'Studeprogrammer', + href: '/admin/study-programmes' } ], }, diff --git a/src/app/admin/studyprogrammes/page.tsx b/src/app/admin/study-programmes/page.tsx similarity index 100% rename from src/app/admin/studyprogrammes/page.tsx rename to src/app/admin/study-programmes/page.tsx diff --git a/src/app/admin/studyprogrammes/studyProgrammeTable.module.scss b/src/app/admin/study-programmes/studyProgrammeTable.module.scss similarity index 100% rename from src/app/admin/studyprogrammes/studyProgrammeTable.module.scss rename to src/app/admin/study-programmes/studyProgrammeTable.module.scss diff --git a/src/app/admin/studyprogrammes/studyProgrammeTable.tsx b/src/app/admin/study-programmes/studyProgrammeTable.tsx similarity index 100% rename from src/app/admin/studyprogrammes/studyProgrammeTable.tsx rename to src/app/admin/study-programmes/studyProgrammeTable.tsx diff --git a/src/app/admin/studyprogrammes/updateStudyProgrammeForm.tsx b/src/app/admin/study-programmes/updateStudyProgrammeForm.tsx similarity index 100% rename from src/app/admin/studyprogrammes/updateStudyProgrammeForm.tsx rename to src/app/admin/study-programmes/updateStudyProgrammeForm.tsx From 839cc40dae5f57b46f7a655e47767ecd4af60a38 Mon Sep 17 00:00:00 2001 From: Theodor Kvalsvik Lauritzen <19690242+theodorklauritzen@users.noreply.github.com> Date: Thu, 7 Nov 2024 20:26:25 +0100 Subject: [PATCH 4/8] fix: build issue --- src/app/admin/omegaid/container.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/admin/omegaid/container.tsx b/src/app/admin/omegaid/container.tsx index 9db68409e..fd4fb8861 100644 --- a/src/app/admin/omegaid/container.tsx +++ b/src/app/admin/omegaid/container.tsx @@ -9,9 +9,9 @@ export default function OmegaIdContainer({ }) { return ({ + successCallback={async (userId) => ({ success: true, - text: `${user.firstname} ${user.lastname}`, + text: `userID: ${userId}`, })} /> } From ede6c4b45f95b38e266f963c689f37b1c963e6a3 Mon Sep 17 00:00:00 2001 From: Theodor Kvalsvik Lauritzen <19690242+theodorklauritzen@users.noreply.github.com> Date: Thu, 7 Nov 2024 20:30:06 +0100 Subject: [PATCH 5/8] fix: linting --- src/app/_components/OmegaId/reader/OmegaIdReader.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/_components/OmegaId/reader/OmegaIdReader.tsx b/src/app/_components/OmegaId/reader/OmegaIdReader.tsx index d1ad5fe22..4e151fccc 100644 --- a/src/app/_components/OmegaId/reader/OmegaIdReader.tsx +++ b/src/app/_components/OmegaId/reader/OmegaIdReader.tsx @@ -5,7 +5,6 @@ import { parseJWT } from '@/jwt/parseJWTClient' import { Html5QrcodeScanner } from 'html5-qrcode' import { useEffect, useState } from 'react' import { v4 as uuid } from 'uuid' -import type { OmegaId } from '@/services/omegaid/Types' import { decomporessOmegaId as decompressOmegaId } from '@/services/omegaid/compress' /** From 801b1e31acc77607df5e7ceee64d36d45ba535df Mon Sep 17 00:00:00 2001 From: Theodor Kvalsvik Lauritzen <19690242+theodorklauritzen@users.noreply.github.com> Date: Thu, 7 Nov 2024 20:52:45 +0100 Subject: [PATCH 6/8] fix: resolve conversations --- default.env | 2 ++ src/app/_components/OmegaId/reader/OmegaIdReader.tsx | 2 +- src/jwt/jwt.ts | 10 +++++----- src/services/omegaid/compress.ts | 2 +- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/default.env b/default.env index ada3d2c30..f7ff6660e 100644 --- a/default.env +++ b/default.env @@ -59,6 +59,8 @@ um/xyw/nKFJcqqMJ71Xq3SY+nA7I0ui4R4W6usx9He6kb5EKlzc9EdVq0w== -----END PUBLIC KEY----- " +JWT_SECRET="hs_maa_gaa" + # Postfix MAIL_DOMAIN=sanctus.omega.ntnu.no MAIL_RELAY_HOST=mailgw.ntnu.no diff --git a/src/app/_components/OmegaId/reader/OmegaIdReader.tsx b/src/app/_components/OmegaId/reader/OmegaIdReader.tsx index 4e151fccc..5ba805b44 100644 --- a/src/app/_components/OmegaId/reader/OmegaIdReader.tsx +++ b/src/app/_components/OmegaId/reader/OmegaIdReader.tsx @@ -2,10 +2,10 @@ import { QRCodeReaderConfig } from './ConfigVars' import styles from './OmegaIdReader.module.scss' import { parseJWT } from '@/jwt/parseJWTClient' +import { decompressOmegaId } from '@/services/omegaid/compress' import { Html5QrcodeScanner } from 'html5-qrcode' import { useEffect, useState } from 'react' import { v4 as uuid } from 'uuid' -import { decomporessOmegaId as decompressOmegaId } from '@/services/omegaid/compress' /** * Renders a component for reading OmegaId QR codes. diff --git a/src/jwt/jwt.ts b/src/jwt/jwt.ts index d9eec9036..d62e9aacd 100644 --- a/src/jwt/jwt.ts +++ b/src/jwt/jwt.ts @@ -25,11 +25,11 @@ export function generateJWT( expiresIn: number, asymetric = false ): string { - if (!process.env.NEXTAUTH_SECRET || !process.env.JWT_PRIVATE_KEY) { + if (!process.env.JWT_SECRET || !process.env.JWT_PRIVATE_KEY) { throw new ServerError('INVALID CONFIGURATION', 'Missing secret for JWT generation') } - return sign(payload, Buffer.from(asymetric ? process.env.JWT_PRIVATE_KEY : process.env.NEXTAUTH_SECRET), { + return sign(payload, asymetric ? process.env.JWT_PRIVATE_KEY : process.env.JWT_SECRET, { audience: aud, algorithm: asymetric ? 'ES256' : 'HS256', issuer: JWT_ISSUER, @@ -44,16 +44,16 @@ export function generateJWT( * @throws {ServerError} If the JWT is expired or invalid. */ export function verifyJWT(token: string, aud?: OmegaJWTAudience): (jwt.JwtPayload & Record) { - if (!process.env.NEXTAUTH_SECRET || !process.env.JWT_PUBLIC_KEY) { + if (!process.env.JWT_SECRET || !process.env.JWT_PUBLIC_KEY) { throw new ServerError( 'INVALID CONFIGURATION', - 'JWT environ variables is not set. Missing NEXTAUTH_SECRET or JWT_PUBLIC_KEY' + 'JWT environ variables is not set. Missing JWT_SECRET or JWT_PUBLIC_KEY' ) } try { const JWTHeader = readJWTPart(token, 0) - let jwtKey = process.env.NEXTAUTH_SECRET + let jwtKey = process.env.JWT_SECRET if (JWTHeader.alg === 'ES256') { jwtKey = process.env.JWT_PUBLIC_KEY } diff --git a/src/services/omegaid/compress.ts b/src/services/omegaid/compress.ts index 6f32b56fd..38a153ea7 100644 --- a/src/services/omegaid/compress.ts +++ b/src/services/omegaid/compress.ts @@ -73,7 +73,7 @@ function decompressPayload(rawdata: string): string { return encodeBase64Url(payloadString) } -export function decomporessOmegaId(rawdata: string): ActionReturn { +export function decompressOmegaId(rawdata: string): ActionReturn { const header = { alg: 'ES256', typ: 'JWT' From 57707389c392f73fe0fdd1b30269da9241fc2b8c Mon Sep 17 00:00:00 2001 From: Theodor Kvalsvik Lauritzen <19690242+theodorklauritzen@users.noreply.github.com> Date: Thu, 7 Nov 2024 21:03:45 +0100 Subject: [PATCH 7/8] chore: Resolve conversations --- src/app/admin/SlideSidebar.tsx | 2 +- src/lib/jwt/jwt.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/admin/SlideSidebar.tsx b/src/app/admin/SlideSidebar.tsx index 24beae2ed..5fb61ac88 100644 --- a/src/app/admin/SlideSidebar.tsx +++ b/src/app/admin/SlideSidebar.tsx @@ -94,7 +94,7 @@ const navigations = [ href: '/admin/classes' }, { - title: 'Studeprogrammer', + title: 'Studieprogrammer', href: '/admin/study-programmes' } ], diff --git a/src/lib/jwt/jwt.ts b/src/lib/jwt/jwt.ts index d62e9aacd..f43ef0e42 100644 --- a/src/lib/jwt/jwt.ts +++ b/src/lib/jwt/jwt.ts @@ -17,6 +17,8 @@ export type JWT> = T & JwtPayloadType['Detailed'] * @param aud - An audience for the token, this is the purpose of the token * @param payload - The payload to be included in the JWT. * @param expiresIn - The expiration time of the JWT in seconds. + * @param asymetric - If this is set to true the JWT token will be signed with a private key, + * and can be verified with a public key. The public key is available for all users * @returns The generated JWT. */ export function generateJWT( From b70b9fa53d2b2b00b0f436eb2616f341443aa391 Mon Sep 17 00:00:00 2001 From: Theodor Kvalsvik Lauritzen <19690242+theodorklauritzen@users.noreply.github.com> Date: Thu, 7 Nov 2024 21:07:22 +0100 Subject: [PATCH 8/8] chore: Add JWT_SECRET to docker compose --- docker-compose.dev.yml | 1 + docker-compose.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 21dca214d..1e06d949c 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -37,6 +37,7 @@ services: DOMAIN: ${DOMAIN} JWT_PRIVATE_KEY: ${JWT_PRIVATE_KEY} JWT_PUBLIC_KEY: ${JWT_PUBLIC_KEY} + JWT_SECRET: ${JWT_SECRET} depends_on: db: condition: service_healthy diff --git a/docker-compose.yml b/docker-compose.yml index c0fbd9bb9..a2c87caca 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -29,6 +29,7 @@ services: DOMAIN: ${DOMAIN} JWT_PRIVATE_KEY: ${JWT_PRIVATE_KEY} JWT_PUBLIC_KEY: ${JWT_PUBLIC_KEY} + JWT_SECRET: ${JWT_SECRET} depends_on: db: condition: service_healthy