mirror of
https://github.com/IT4Change/gradido.git
synced 2026-02-06 09:56:05 +00:00
try to make oneTimeCode more robust
This commit is contained in:
parent
47b38ac58f
commit
e8ef1bc310
@ -7,7 +7,7 @@ import { AuthenticationClient as V1_0_AuthenticationClient } from '@/federation/
|
||||
import { ensureUrlEndsWithSlash } from 'core'
|
||||
|
||||
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
|
||||
import { encryptAndSign, OpenConnectionJwtPayloadType } from 'shared'
|
||||
import { communityAuthenticatedSchema, encryptAndSign, OpenConnectionJwtPayloadType } from 'shared'
|
||||
import { getLogger } from 'log4js'
|
||||
import { AuthenticationClientFactory } from './client/AuthenticationClientFactory'
|
||||
import { EncryptedTransferArgs } from 'core'
|
||||
@ -34,13 +34,10 @@ export async function startCommunityAuthentication(
|
||||
methodLogger.debug('started with comB:', new CommunityLoggingView(comB))
|
||||
// check if communityUuid is not a valid v4Uuid
|
||||
try {
|
||||
if (
|
||||
comB &&
|
||||
((comB.communityUuid === null && comB.authenticatedAt === null) ||
|
||||
(comB.communityUuid !== null &&
|
||||
(!validateUUID(comB.communityUuid) ||
|
||||
versionUUID(comB.communityUuid!) !== 4)))
|
||||
) {
|
||||
// communityAuthenticatedSchema.safeParse return true
|
||||
// - if communityUuid is a valid v4Uuid and
|
||||
// - if authenticatedAt is a valid date
|
||||
if (comB && !communityAuthenticatedSchema.safeParse(comB).success) {
|
||||
methodLogger.debug('comB.uuid is null or is a not valid v4Uuid...', comB.communityUuid || 'null', comB.authenticatedAt || 'null')
|
||||
const client = AuthenticationClientFactory.getInstance(fedComB)
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@ import {
|
||||
FederatedCommunity as DbFederatedCommunity,
|
||||
FederatedCommunityLoggingView,
|
||||
getHomeCommunity,
|
||||
getNotReachableCommunities,
|
||||
} from 'database'
|
||||
import { IsNull } from 'typeorm'
|
||||
|
||||
@ -11,7 +12,7 @@ import { FederationClient as V1_0_FederationClient } from '@/federation/client/1
|
||||
import { PublicCommunityInfo } from '@/federation/client/1_0/model/PublicCommunityInfo'
|
||||
import { FederationClientFactory } from '@/federation/client/FederationClientFactory'
|
||||
import { LogError } from '@/server/LogError'
|
||||
import { createKeyPair } from 'shared'
|
||||
import { createKeyPair, uint32Schema } from 'shared'
|
||||
import { getLogger } from 'log4js'
|
||||
import { startCommunityAuthentication } from './authenticateCommunities'
|
||||
import { PublicCommunityInfoLoggingView } from './client/1_0/logging/PublicCommunityInfoLogging.view'
|
||||
@ -27,6 +28,16 @@ export async function startValidateCommunities(timerInterval: number): Promise<v
|
||||
// delete all foreign federated community entries to avoid increasing validation efforts and log-files
|
||||
await DbFederatedCommunity.delete({ foreign: true })
|
||||
|
||||
// clean community_uuid and authenticated_at fields for community with one-time-code in community_uuid field
|
||||
const notReachableCommunities = await getNotReachableCommunities()
|
||||
for (const community of notReachableCommunities) {
|
||||
if (uint32Schema.safeParse(Number(community.communityUuid)).success) {
|
||||
community.communityUuid = null
|
||||
community.authenticatedAt = null
|
||||
await DbCommunity.save(community)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: replace the timer-loop by an event-based communication to verify announced foreign communities
|
||||
// better to use setTimeout twice than setInterval once -> see https://javascript.info/settimeout-setinterval
|
||||
setTimeout(async function run() {
|
||||
|
||||
@ -60,4 +60,13 @@ export async function getReachableCommunities(
|
||||
],
|
||||
order,
|
||||
})
|
||||
}
|
||||
|
||||
export async function getNotReachableCommunities(
|
||||
order?: FindOptionsOrder<DbCommunity>
|
||||
): Promise<DbCommunity[]> {
|
||||
return await DbCommunity.find({
|
||||
where: { authenticatedAt: IsNull(), foreign: true },
|
||||
order,
|
||||
})
|
||||
}
|
||||
@ -127,21 +127,27 @@ export class AuthenticationResolver {
|
||||
methodLogger.debug(`authenticate() via apiVersion=1_0 ...`, args)
|
||||
try {
|
||||
const authArgs = await interpretEncryptedTransferArgs(args) as AuthenticationJwtPayloadType
|
||||
methodLogger.debug(`interpreted authentication payload...authArgs:`, authArgs)
|
||||
if (!authArgs) {
|
||||
const errmsg = `invalid authentication payload of requesting community with publicKey` + args.publicKey
|
||||
methodLogger.error(errmsg)
|
||||
// no infos to the caller
|
||||
return null
|
||||
}
|
||||
|
||||
if (!uint32Schema.safeParse(Number(authArgs.oneTimeCode)).success) {
|
||||
const errmsg = `invalid oneTimeCode: ${authArgs.oneTimeCode} for community with publicKey ${authArgs.publicKey}, expect uint32`
|
||||
methodLogger.error(errmsg)
|
||||
// no infos to the caller
|
||||
return null
|
||||
}
|
||||
|
||||
methodLogger.debug(`search community per oneTimeCode:`, authArgs.oneTimeCode)
|
||||
const authCom = await DbCommunity.findOneByOrFail({ communityUuid: authArgs.oneTimeCode })
|
||||
if (authCom) {
|
||||
methodLogger.debug('found authCom:', new CommunityLoggingView(authCom))
|
||||
methodLogger.debug('authCom.publicKey', authCom.publicKey.toString('hex'))
|
||||
methodLogger.debug('args.publicKey', args.publicKey)
|
||||
if (authCom.publicKey.compare(Buffer.from(args.publicKey, 'hex')) !== 0) {
|
||||
const errmsg = `corrupt authentication call detected, oneTimeCode: ${authArgs.oneTimeCode} doesn't belong to caller: ${args.publicKey}`
|
||||
methodLogger.error(errmsg)
|
||||
|
||||
@ -15,6 +15,7 @@ import { randombytes_random } from 'sodium-native'
|
||||
import { AuthenticationClient as V1_0_AuthenticationClient } from '@/client/1_0/AuthenticationClient'
|
||||
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
|
||||
import { AuthenticationJwtPayloadType, AuthenticationResponseJwtPayloadType, encryptAndSign, OpenConnectionCallbackJwtPayloadType, uint32Schema, uuidv4Schema, verifyAndDecrypt } from 'shared'
|
||||
import { FEDERATION_AUTHENTICATION_TIMEOUT_MS } from 'shared'
|
||||
|
||||
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.graphql.api.1_0.util.authenticateCommunity`)
|
||||
|
||||
@ -45,9 +46,12 @@ export async function startOpenConnectionCallback(
|
||||
if (uuidv4Schema.safeParse(comA.communityUuid).success) {
|
||||
methodLogger.debug('Community UUID is already a valid UUID')
|
||||
return
|
||||
// check for still ongoing authentication, but with timeout
|
||||
} else if (uint32Schema.safeParse(Number(comA.communityUuid)).success) {
|
||||
methodLogger.debug('Community UUID is still in authentication...oneTimeCode=', comA.communityUuid)
|
||||
return
|
||||
if (comA.updatedAt && (Date.now() - comA.updatedAt.getTime()) < FEDERATION_AUTHENTICATION_TIMEOUT_MS) {
|
||||
methodLogger.debug('Community UUID is still in authentication...oneTimeCode=', comA.communityUuid)
|
||||
return
|
||||
}
|
||||
}
|
||||
// TODO: make sure it is unique
|
||||
const oneTimeCode = randombytes_random().toString()
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
export const DECAY_START_TIME = new Date('2021-05-13T17:46:31Z')
|
||||
export const SECONDS_PER_YEAR_GREGORIAN_CALENDER = 31556952.0
|
||||
export const LOG4JS_BASE_CATEGORY_NAME = 'shared'
|
||||
export const REDEEM_JWT_TOKEN_EXPIRATION = '10m'
|
||||
export const REDEEM_JWT_TOKEN_EXPIRATION = '10m'
|
||||
// 10 minutes
|
||||
export const FEDERATION_AUTHENTICATION_TIMEOUT_MS = 60 * 60 * 1000 * 10
|
||||
@ -1,5 +1,6 @@
|
||||
export * from './schema'
|
||||
export * from './enum'
|
||||
export * from './const'
|
||||
export * from './helper'
|
||||
export * from './logic/decay'
|
||||
export * from './jwt/JWT'
|
||||
|
||||
35
shared/src/schema/community.schema.test.ts
Normal file
35
shared/src/schema/community.schema.test.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { communityAuthenticatedSchema } from './community.schema'
|
||||
import { describe, it, expect } from 'bun:test'
|
||||
|
||||
|
||||
describe('communityAuthenticatedSchema', () => {
|
||||
it('should return an error if communityUuid is not a uuidv4', () => {
|
||||
const data = communityAuthenticatedSchema.safeParse({
|
||||
communityUuid: '1234567890',
|
||||
authenticatedAt: new Date(),
|
||||
})
|
||||
|
||||
expect(data.success).toBe(false)
|
||||
expect(data.error?.issues[0].path).toEqual(['communityUuid'])
|
||||
})
|
||||
|
||||
it('should return an error if authenticatedAt is not a date', () => {
|
||||
const data = communityAuthenticatedSchema.safeParse({
|
||||
communityUuid: uuidv4(),
|
||||
authenticatedAt: '2022-01-01',
|
||||
})
|
||||
|
||||
expect(data.success).toBe(false)
|
||||
expect(data.error?.issues[0].path).toEqual(['authenticatedAt'])
|
||||
})
|
||||
|
||||
it('should return no error for valid data and valid uuid4', () => {
|
||||
const data = communityAuthenticatedSchema.safeParse({
|
||||
communityUuid: uuidv4(),
|
||||
authenticatedAt: new Date(),
|
||||
})
|
||||
|
||||
expect(data.success).toBe(true)
|
||||
})
|
||||
})
|
||||
7
shared/src/schema/community.schema.ts
Normal file
7
shared/src/schema/community.schema.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { object, date } from 'zod'
|
||||
import { uuidv4Schema } from './base.schema'
|
||||
|
||||
export const communityAuthenticatedSchema = object({
|
||||
communityUuid: uuidv4Schema,
|
||||
authenticatedAt: date(),
|
||||
})
|
||||
@ -1,2 +1,3 @@
|
||||
export * from './user.schema'
|
||||
export * from './base.schema'
|
||||
export * from './base.schema'
|
||||
export * from './community.schema'
|
||||
Loading…
x
Reference in New Issue
Block a user