Skip to content

Commit

Permalink
Use the "hd" claim for checking whether a user is part of a domain
Browse files Browse the repository at this point in the history
  • Loading branch information
raimohanska committed Jan 2, 2024
1 parent 37ca291 commit ef0a731
Show file tree
Hide file tree
Showing 7 changed files with 28 additions and 16 deletions.
15 changes: 8 additions & 7 deletions backend/src/generic-oidc-auth.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import { isLeft, Left, left } from "fp-ts/lib/Either"
import { Request, Response } from "express"
import * as t from "io-ts"
import { PathReporter } from "io-ts/lib/PathReporter"
import JWT from "jsonwebtoken"
import { OAuthAuthenticatedUser } from "../../common/src/authenticated-user"
import { optional } from "../../common/src/domain"
import { decodeOrThrow } from "./decodeOrThrow"
import { getEnv } from "./env"
import { AuthProvider } from "./oauth"
import { ROOT_URL } from "./host-config"
import { optional } from "../../common/src/domain"
import { AuthProvider } from "./oauth"
import { REQUIRE_AUTH } from "./require-auth"
import { Request, Response } from "express"
import { decodeOrThrow } from "./decodeOrThrow"

type GenericOAuthConfig = {
OIDC_CONFIG_URL: string
Expand Down Expand Up @@ -51,12 +49,13 @@ export function GenericOIDCAuthProvider(config: GenericOAuthConfig): AuthProvide
const body = await response.json()

const idToken = JWT.decode(body.id_token)
console.log(JSON.stringify(idToken, null, 2))
//console.log(JSON.stringify(idToken, null, 2))
const user = decodeOrThrow(IdToken, idToken)
return {
email: user.email,
name: "name" in user ? user.name : user.preferred_username,
picture: user.picture ?? undefined,
domain: user.hd ?? null,
}
}

Expand Down Expand Up @@ -113,10 +112,12 @@ const IdToken = t.union([
email: t.string,
name: t.string,
picture: optional(t.string),
hd: optional(t.string),
}),
t.type({
email: t.string,
preferred_username: t.string,
picture: optional(t.string),
hd: optional(t.string),
}),
])
1 change: 1 addition & 0 deletions backend/src/google-auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export const GoogleAuthProvider = (googleConfig: GoogleConfig): AuthProvider =>
name: idToken.name,
email,
picture: idToken.picture,
domain: idToken.hd ?? null,
}
}

Expand Down
7 changes: 6 additions & 1 deletion backend/src/http-session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,12 @@ export function getAuthenticatedUser(req: IncomingMessage): LoginInfo | null {
export function getAuthenticatedUserFromJWT(jwt: string): LoginInfo | null {
try {
JWT.verify(jwt, secret)
return JWT.decode(jwt) as LoginInfo
const loginInfo = JWT.decode(jwt) as LoginInfo
if (loginInfo.domain === undefined) {
console.log("Rejecting legacy token without domain")
return null
}
return loginInfo
} catch (e) {
console.warn("Token verification failed", jwt, e)
}
Expand Down
4 changes: 3 additions & 1 deletion backend/src/websocket-sessions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
} from "../../common/src/domain"
import { maybeGetBoard, ServerSideBoardState } from "./board-state"
import { getBoardHistory } from "./board-store"
import { LoginInfo } from "./http-session"
import { randomProfession } from "./professions"
import { getUserIdForEmail } from "./user-store"
import { StringifyCache, WsWrapper } from "./ws-wrapper"
Expand Down Expand Up @@ -266,7 +267,7 @@ export function setNicknameForSession(event: SetNickname, origin: WsWrapper) {
}

export async function setVerifiedUserForSession(
event: UserLoggedIn | OAuthAuthenticatedUser,
event: OAuthAuthenticatedUser,
session: UserSession,
): Promise<EventUserInfoAuthenticated> {
const userId = await getUserIdForEmail(event.email)
Expand All @@ -276,6 +277,7 @@ export async function setVerifiedUserForSession(
name: event.name,
email: event.email,
picture: event.picture,
domain: event.domain,
userId,
}
if (session.boardSession) {
Expand Down
1 change: 1 addition & 0 deletions common/src/authenticated-user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export type OAuthAuthenticatedUser = {
name: string
email: string
picture?: string
domain: string | null
}
11 changes: 5 additions & 6 deletions common/src/domain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,6 @@ export type BoardAccessPolicyDefined = t.TypeOf<typeof BoardAccessPolicyDefinedC
export const BoardAccessPolicyCodec = t.union([t.undefined, BoardAccessPolicyDefinedCodec])
export type BoardAccessPolicy = t.TypeOf<typeof BoardAccessPolicyCodec>

export type AuthorizedParty = AuthorizedByEmailAddress | AuthorizedByDomain
export type AuthorizedByEmailAddress = { email: string }
export type AuthorizedByDomain = { domain: string }

export type EventUserInfo = UnidentifiedUserInfo | SystemUserInfo | EventUserInfoAuthenticated

export type UnidentifiedUserInfo = { nickname: string; userType: "unidentified" }
Expand All @@ -88,6 +84,7 @@ export type SessionUserInfoAuthenticated = {
email: string
picture: string | undefined
userId: string
domain: string | null
}

export type UserSessionInfo = SessionUserInfo & {
Expand Down Expand Up @@ -607,7 +604,7 @@ export function getBoardAttributes(board: Board, userInfo?: EventUserInfo): Boar

export const BOARD_ITEM_BORDER_MARGIN = 0.5

export function checkBoardAccess(accessPolicy: BoardAccessPolicy | undefined, userInfo: EventUserInfo): AccessLevel {
export function checkBoardAccess(accessPolicy: BoardAccessPolicy | undefined, userInfo: SessionUserInfo): AccessLevel {
if (!accessPolicy) return "read-write"
let accessLevel: AccessLevel = accessPolicy.publicWrite
? "read-write"
Expand All @@ -618,12 +615,14 @@ export function checkBoardAccess(accessPolicy: BoardAccessPolicy | undefined, us
return accessLevel
}
const email = userInfo.email
const domain = userInfo.domain

const defaultAccess = "read-write"
for (let entry of accessPolicy.allowList) {
const nextLevel =
"email" in entry && entry.email === email
? entry.access || defaultAccess
: "domain" in entry && email.endsWith(entry.domain)
: "domain" in entry && domain === entry.domain
? entry.access || defaultAccess
: "none"
accessLevel = combineAccessLevels(accessLevel, nextLevel)
Expand Down
5 changes: 4 additions & 1 deletion frontend/src/store/board-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
newISOTimeStamp,
PersistableBoardItemEvent,
ServerConfig,
SessionUserInfo,
TransientBoardItemEvent,
UIEvent,
UserSessionInfo,
Expand Down Expand Up @@ -512,14 +513,16 @@ export function BoardStore(
}
}

export function sessionState2UserInfo(state: UserSessionState): EventUserInfo {
export function sessionState2UserInfo(state: UserSessionState): SessionUserInfo {
if (state.status === "logged-in") {
return {
userType: "authenticated",
email: state.email,
nickname: state.nickname,
name: state.name,
userId: state.userId,
domain: state.domain,
picture: state.picture,
}
} else {
return {
Expand Down

0 comments on commit ef0a731

Please sign in to comment.