introduce validUntil in redeem-jwt-token payload

This commit is contained in:
clauspeterhuebner 2025-04-24 17:26:14 +02:00
parent 322e4ca37f
commit 7104de0254
9 changed files with 48 additions and 8 deletions

View File

@ -10,6 +10,7 @@ export class DisbursementJwtPayloadType extends JwtPayloadType {
redeemcode: string
amount: string
memo: string
validuntil: string
constructor(
senderCom: string,
@ -18,6 +19,7 @@ export class DisbursementJwtPayloadType extends JwtPayloadType {
code: string,
amount: string,
memo: string,
validUntil: string,
) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
super()
@ -29,5 +31,6 @@ export class DisbursementJwtPayloadType extends JwtPayloadType {
this.redeemcode = code
this.amount = amount
this.memo = memo
this.validuntil = validUntil
}
}

View File

@ -1,5 +1,7 @@
import { JWTPayload } from 'jose'
import { CONFIG } from '@/config'
export class JwtPayloadType implements JWTPayload {
iat?: number | undefined
exp?: number | undefined
@ -14,6 +16,6 @@ export class JwtPayloadType implements JWTPayload {
expiration: string // in minutes (format: 10m for ten minutes)
constructor() {
this.tokentype = 'unknown jwt type'
this.expiration = '10m'
this.expiration = CONFIG.REDEEM_JWT_TOKEN_EXPIRATION || '10m'
}
}

View File

@ -27,6 +27,7 @@ const server = {
PORT: process.env.PORT ?? 4000,
JWT_SECRET: process.env.JWT_SECRET ?? 'secret123',
JWT_EXPIRES_IN: process.env.JWT_EXPIRES_IN ?? '10m',
REDEEM_JWT_TOKEN_EXPIRATION: process.env.REDEEM_JWT_TOKEN_EXPIRATION ?? '10m',
GRAPHIQL: process.env.GRAPHIQL === 'true' || false,
GDT_ACTIVE: process.env.GDT_ACTIVE === 'true' || false,
GDT_API_URL: process.env.GDT_API_URL ?? 'https://gdt.gradido.net',

View File

@ -363,5 +363,20 @@ export const schema = Joi.object({
.required()
.description('Time for JWT token to expire, auto logout'),
REDEEM_JWT_TOKEN_EXPIRATION: Joi.alternatives()
.try(
Joi.string()
.pattern(/^\d+[smhdw]$/)
.description(
'Expiration time for x-community redeem JWT token, in format like "10m", "1h", "1d"',
)
.default('10m'),
Joi.number()
.positive()
.description('Expiration time for x-community redeem JWT token in minutes'),
)
.required()
.description('Time for x-community redeem JWT token to expire'),
WEBHOOK_ELOPAGE_SECRET: Joi.string().description("isn't really used any more").optional(),
})

View File

@ -26,9 +26,7 @@ export class DisbursementLink {
this.amount = new Decimal(disbursementPayload.amount)
this.memo = disbursementPayload.memo
this.code = disbursementPayload.redeemcode
if (disbursementPayload.exp) {
this.validUntil = new Date(disbursementPayload.exp * 1000)
}
this.validUntil = new Date(disbursementPayload.validuntil)
}
@Field(() => Community)

View File

@ -405,6 +405,7 @@ export class TransactionLinkResolver {
@Arg('memo') memo: string,
@Arg('firstName', { nullable: true }) firstName?: string,
@Arg('alias', { nullable: true }) alias?: string,
@Arg('validUntil', { nullable: true }) validUntil?: Date,
): Promise<string> {
logger.debug('TransactionLinkResolver.queryRedeemJwt... args=', {
gradidoID,
@ -416,6 +417,7 @@ export class TransactionLinkResolver {
memo,
firstName,
alias,
validUntil,
})
const disbursementJwtPayloadType = new DisbursementJwtPayloadType(
@ -425,6 +427,7 @@ export class TransactionLinkResolver {
code,
amount,
memo,
validUntil?.toISOString() ?? '',
)
// TODO:encode/sign the jwt normally with the private key of the sender/home community, but interims with uuid
const homeCom = await getHomeCommunity()
@ -474,6 +477,7 @@ export class TransactionLinkResolver {
async queryDisbursementLink(code: string): Promise<DisbursementLink> {
logger.debug('TransactionLinkResolver.queryDisbursementLink... disbursement jwt-token found')
// decode token first to get the senderCommunityUuid as input for verify token
const decodedPayload = decode(code)
logger.debug('TransactionLinkResolver.queryDisbursementLink... decodedPayload=', decodedPayload)
if (
@ -487,6 +491,7 @@ export class TransactionLinkResolver {
decodedPayload.redeemcode as string,
decodedPayload.amount as string,
decodedPayload.memo as string,
decodedPayload.validuntil as string,
)
logger.debug(
'TransactionLinkResolver.queryDisbursementLink... disburseJwtPayload=',
@ -508,12 +513,21 @@ export class TransactionLinkResolver {
)
let verifiedPayload: DisbursementJwtPayloadType | null = null
if (jwtPayload !== null) {
if (jwtPayload.exp && new Date(jwtPayload.exp * 1000) < new Date()) {
throw new LogError(
'Redeem JWT-Token expired! jwtPayload.exp=',
new Date(jwtPayload.exp * 1000),
if (jwtPayload.exp !== undefined) {
const expDate = new Date(jwtPayload.exp * 1000)
logger.debug(
'TransactionLinkResolver.queryDisbursementLink... expDate, exp =',
expDate,
jwtPayload.exp,
)
if (expDate < new Date()) {
throw new LogError('Redeem JWT-Token expired! jwtPayload.exp=', expDate)
}
} else if (jwtPayload.tokentype === DisbursementJwtPayloadType.REDEEM_ACTIVATION_TYPE) {
logger.debug(
'TransactionLinkResolver.queryDisbursementLink... jwtPayload.tokentype=',
jwtPayload.tokentype,
)
verifiedPayload = new DisbursementJwtPayloadType(
jwtPayload.sendercommunityuuid as string,
jwtPayload.sendergradidoid as string,
@ -521,6 +535,7 @@ export class TransactionLinkResolver {
jwtPayload.redeemcode as string,
jwtPayload.amount as string,
jwtPayload.memo as string,
jwtPayload.validuntil as string,
)
logger.debug(
'TransactionLinkResolver.queryDisbursementLink... nach verify verifiedPayload=',
@ -539,6 +554,7 @@ export class TransactionLinkResolver {
decodedPayload.redeemcode as string,
decodedPayload.amount as string,
decodedPayload.memo as string,
decodedPayload.validuntil as string,
)
} else {
// TODO: as long as the verification fails, fallback to simply decoded payload

View File

@ -138,6 +138,7 @@ async function onSwitch(event) {
memo: props.linkData.memo,
firstName: props.linkData.senderUser?.firstName,
alias: props.linkData.senderUser?.alias,
validUntil: props.linkData.validUntil,
})
try {
const { data } = await createRedeemJwt({
@ -150,6 +151,7 @@ async function onSwitch(event) {
memo: props.linkData.memo,
firstName: props.linkData.senderUser?.firstName,
alias: props.linkData.senderUser?.alias,
validUntil: props.linkData.validUntil,
})
console.log('RedeemCommunitySelection.onSwitch... response=', data)
if (!data?.createRedeemJwt) {

View File

@ -210,6 +210,7 @@ export const createRedeemJwtMutation = gql`
$memo: String!
$firstName: String
$alias: String
$validUntil: Date
) {
createRedeemJwt(
gradidoID: $gradidoID
@ -221,6 +222,7 @@ export const createRedeemJwtMutation = gql`
memo: $memo
firstName: $firstName
alias: $alias
validUntil: $validUntil
)
}
`

View File

@ -56,6 +56,7 @@ const { d, t } = useI18n()
const linkData = ref({
__typename: 'TransactionLink',
validUntil: null,
amount: 0,
memo: '',
senderCommunity: null,