Skip to content

Commit

Permalink
fix merge
Browse files Browse the repository at this point in the history
  • Loading branch information
AssafKr committed Jul 31, 2023
1 parent 8f77f69 commit 493e907
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 24 deletions.
9 changes: 7 additions & 2 deletions tunnel-server/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import internal from 'stream'
import { Logger } from 'pino'
import { match } from 'ts-pattern'
import { SessionManager } from './seesion'
import { Claims, JwtAuthenticator, authenticator, unauthorized } from './auth'
import { Claims, JwtAuthenticator, authenticator, envToIssuerToKeyData, unauthorized } from './auth'
import { PreviewEnvStore } from './preview-env'
import { replaceHostname } from './url'

Expand Down Expand Up @@ -63,7 +63,7 @@ export const app = ({ isProxyRequest, proxyHandlers, sessionManager, baseUrl, en
}
const session = sessionManager(req.raw, res.raw, env.publicKeyThumbprint)
if (!session.user) {
const auth = authenticator([JwtAuthenticator(env)])
const auth = authenticator([JwtAuthenticator(envToIssuerToKeyData(env))])
const result = await auth(req.raw)
if (!result.isAuthenticated) {
return unauthorized(res.raw)
Expand All @@ -73,4 +73,9 @@ export const app = ({ isProxyRequest, proxyHandlers, sessionManager, baseUrl, en
}
return await res.redirect(new URL(returnPath, replaceHostname(baseUrl, `${envId}.${baseUrl.hostname}`)).toString())
})
.get('/tunnels', {}, async (req, res) => {
// const auth = authenticator([JwtAuthenticator((iss) => ({pk: }))])
const envs = await envStore.getAll()
void res.send(envs)
})
.get('/healthz', { logLevel: 'warn' }, async () => 'OK')
66 changes: 46 additions & 20 deletions tunnel-server/src/auth.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { IncomingMessage, ServerResponse } from 'http'
import { JWTPayload, calculateJwkThumbprintUri, decodeJwt, exportJWK, jwtVerify } from 'jose'
import { JWTPayload, decodeJwt, jwtVerify } from 'jose'
import { match } from 'ts-pattern'
import { z } from 'zod'
import Cookies from 'cookies'
import { KeyObject } from 'crypto'
import { PreviewEnv } from './preview-env'

export const claimsSchema = z.object({
Expand Down Expand Up @@ -60,7 +61,12 @@ function extractAuthorizationHeader(req: IncomingMessage): AuthorizationHeader |
return undefined
}

export function JwtAuthenticator(env: PreviewEnv) {
type IssuerToKeyData = (iss?: string) => {
pk: KeyObject
extractClaims: (token: JWTPayload) => Claims
}

export function JwtAuthenticator(issuerToKeyData: IssuerToKeyData) {
return async (req: IncomingMessage):Promise<AuthenticationResult> => {
const auth = extractAuthorizationHeader(req)
const jwt = match(auth)
Expand All @@ -73,27 +79,30 @@ export function JwtAuthenticator(env: PreviewEnv) {
}

const { iss } = decodeJwt(jwt)
const { issuer, publicKey, extractClaims } = await match(iss).when(() => iss?.startsWith('preevy://'), async () => {
const thumbprint = await calculateJwkThumbprintUri(await exportJWK(env.publicKey))
return {
publicKey: env.publicKey,
issuer: `preevy://${thumbprint}`,
extractClaims: (token:JWTPayload) => ({
role: 'admin',
type: 'profile',
exp: token.exp,
scopes: ['admin'],
sub: `preevy-profile:${env.publicKeyThumbprint}`,
}),
}
}).otherwise(async () => {
throw new Error('invalid issuer')
})

const token = await jwtVerify(jwt, publicKey, { issuer })
// const { issuer, publicKey, extractClaims } = await match(iss).when(() => iss?.startsWith('preevy://'), async () => {
// const thumbprint = await calculateJwkThumbprintUri(await exportJWK(env.publicKey))
// return {
// publicKey: env.publicKey,
// issuer: `preevy://${thumbprint}`,
// extractClaims: (token:JWTPayload) => ({
// role: 'admin',
// type: 'profile',
// exp: token.exp,
// scopes: ['admin'],
// sub: `preevy-profile:${env.publicKeyThumbprint}`,
// }),
// }
// }).otherwise(async () => {
// throw new Error('invalid issuer')
// })

const { pk, extractClaims } = issuerToKeyData(iss)

const token = await jwtVerify(jwt, pk, { issuer: iss })
if (!token) {
throw new Error('invalid token')
}

return {
method: { type: 'header', header: 'authorization' },
isAuthenticated: true,
Expand All @@ -117,3 +126,20 @@ export const unauthorized = (res: ServerResponse<IncomingMessage>) => {
res.statusCode = 401
res.end('Unauthorized')
}

export const envToIssuerToKeyData = (env: PreviewEnv): IssuerToKeyData => iss => {
if (iss === `preevy://${env.publicKeyThumbprint}`) {
return {
pk: env.publicKey,
extractClaims: token => ({
role: 'admin',
type: 'profile',
exp: token.exp,
scopes: ['admin'],
sub: `preevy-profile:${env.publicKeyThumbprint}`,
}),
}
}

throw new Error('invalid issuer')
}
2 changes: 2 additions & 0 deletions tunnel-server/src/preview-env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export type PreviewEnvStore = {
set: (key: string, env: PreviewEnv) => Promise<void>
has: (key: string) => Promise<boolean>
delete: (key: string) => Promise<boolean>
getAll: () => Promise<{[key: string]: PreviewEnv}>
}

export const inMemoryPreviewEnvStore = (initial?: Record<string, PreviewEnv>): PreviewEnvStore => {
Expand All @@ -25,5 +26,6 @@ export const inMemoryPreviewEnvStore = (initial?: Record<string, PreviewEnv>): P
},
has: async key => map.has(key),
delete: async key => map.delete(key),
getAll: async () => Object.fromEntries< PreviewEnv>(map),
}
}
4 changes: 2 additions & 2 deletions tunnel-server/src/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import internal from 'stream'
import type { Logger } from 'pino'
import { PreviewEnvStore } from './preview-env'
import { requestsCounter } from './metrics'
import { Claims, authenticator, JwtAuthenticator, unauthorized } from './auth'
import { Claims, authenticator, JwtAuthenticator, unauthorized, envToIssuerToKeyData } from './auth'
import { SessionManager } from './seesion'

export const isProxyRequest = (
Expand Down Expand Up @@ -74,7 +74,7 @@ export function proxyHandlers({
const session = sessionManager(req, res, env.publicKeyThumbprint)
if (env.access === 'private') {
if (!session.user) {
const authenticate = authenticator([JwtAuthenticator(env)])
const authenticate = authenticator([JwtAuthenticator(envToIssuerToKeyData(env))])
try {
const authResult = await authenticate(req)
if (!authResult.isAuthenticated) {
Expand Down

0 comments on commit 493e907

Please sign in to comment.