Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions core/wallet-auth/src/auth-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import { decodeJwt } from 'jose'
import { AuthContext } from './auth-service'
import { providerErrors } from '@canton-network/core-rpc-errors'
import { Logger } from '@canton-network/core-types'
import { Idp } from './config/schema.js'

export function assertConnected(
authContext: AuthContext | undefined
Expand Down Expand Up @@ -115,6 +117,39 @@ export async function fetchOidcUserInfo(
return (await userInfoResponse.json()) as OidcUserInfo
}

/**
* Resolve the user email from the OIDC userinfo endpoint.
*
* @param authContext - The authentication context
* @param idp - The IDP configuration
* @param logger - The logger
* @returns The user email, or undefined if the email is not found
*/
export async function resolveUserEmail(
authContext: AuthContext,
idp: Idp,
logger?: Logger
): Promise<string | undefined> {
if (authContext.email) {
return authContext.email
}

try {
if (idp.type !== 'oauth') {
return undefined
}

const userInfo = await fetchOidcUserInfo(
idp.configUrl,
authContext.accessToken
)
return userInfo?.email
} catch (error) {
logger?.warn(error, 'Failed to resolve user email from OIDC userinfo')
return undefined
}
}

/**
* Determine if a given JWT is still valid based on its expiry time.
*
Expand Down
12 changes: 8 additions & 4 deletions wallet-gateway/remote/src/auth/jwt-auth-service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) 2025-2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { AuthService } from '@canton-network/core-wallet-auth'
import { AuthService, resolveUserEmail } from '@canton-network/core-wallet-auth'
import { Store } from '@canton-network/core-wallet-store'
import { createRemoteJWKSet, decodeJwt, jwtVerify } from 'jose'
import { Logger } from 'pino'
Expand Down Expand Up @@ -125,12 +125,16 @@ export const jwtAuthService = (store: Store, logger: Logger): AuthService => ({
},
'JWT verified'
)
const email = getEmail(decoded.email)
return {

const authContext = {
userId: payload.sub,
accessToken: jwt,
...(email ? { email } : {}),
}

const email =
getEmail(decoded.email) ??
(await resolveUserEmail(authContext, idp, logger))
return email ? { ...authContext, email } : authContext
} catch (error) {
if (error instanceof Error) {
logger.warn(error, `Failed to verify token: ${error.message}`)
Expand Down
8 changes: 3 additions & 5 deletions wallet-gateway/remote/src/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,8 @@ import {
migrator as signingMigrator,
} from '@canton-network/core-signing-store-sql'
import { ConfigUtils } from './config/ConfigUtils.js'
import {
SigningDriverInterface,
SigningProvider,
} from '@canton-network/core-signing-lib'
import { SigningProvider } from '@canton-network/core-signing-lib'
import type { SigningDrivers } from './signing/signing-drivers.js'
import { ParticipantSigningDriver } from '@canton-network/core-signing-participant'
import { InternalSigningDriver } from '@canton-network/core-signing-internal'
import DfnsSigningProvider from '@canton-network/core-signing-dfns'
Expand Down Expand Up @@ -284,7 +282,7 @@ export async function initialize(opts: CliOptions, logger: Logger) {
const keyInfo = { apiKey, apiSecret }
const userApiKeys = new Map([['user', keyInfo]])

const drivers: Partial<Record<SigningProvider, SigningDriverInterface>> = {
const drivers: SigningDrivers = {
[SigningProvider.PARTICIPANT]: new ParticipantSigningDriver(),
[SigningProvider.WALLET_KERNEL]: new InternalSigningDriver(
signingStore
Expand Down
Loading
Loading