Skip to content

Commit

Permalink
Merge pull request #350 from nevermined-io/feat/support-download-with…
Browse files Browse the repository at this point in the history
…-sessionKey

feat: update node to support download with session keys
  • Loading branch information
eruizgar91 authored Aug 8, 2024
2 parents a13402b + 32c05aa commit dfbb693
Show file tree
Hide file tree
Showing 8 changed files with 179 additions and 17 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -403,4 +403,6 @@ circuits
*.tgz
.yalc*
.env*
accounts/provider-staging.json
accounts/provider-staging.json
accounts/rsa_priv_key-staging.pem
accounts/rsa_pub_key-staging.pem
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "node-ts",
"version": "3.0.7",
"version": "3.0.8",
"description": "Nevermined Node",
"main": "main.ts",
"scripts": {
Expand Down Expand Up @@ -28,6 +28,7 @@
"artifacts:arbitrum-sepolia": "sh ./scripts/download-artifacts.sh v3.5.6 arbitrum-sepolia"
},
"dependencies": {
"@ambire/signature-validator": "^1.3.1",
"@nestjs/axios": "^3.0.2",
"@nestjs/common": "^10.3.8",
"@nestjs/core": "^10.3.8",
Expand All @@ -38,7 +39,7 @@
"@nestjs/typeorm": "^10.0.2",
"@nevermined-io/argo-workflows-api": "^0.1.3",
"@nevermined-io/passport-nevermined": "^0.3.0",
"@nevermined-io/sdk": "3.0.14",
"@nevermined-io/sdk": "3.0.24",
"@sideway/address": "^5.0.0",
"@sideway/formula": "^3.0.1",
"@sideway/pinpoint": "^2.0.0",
Expand Down
21 changes: 21 additions & 0 deletions src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Public } from '../common/decorators/auth.decorator'
import { AuthService } from './auth.service'
import { LoginDto } from './dto/login.dto'
import { NeverminedGuard } from './nvm.guard'
import { SessionKeyAuthGuard } from '../common/guards/auth/session-key.guard'

@ApiTags('Auth')
@Controller()
Expand All @@ -29,4 +30,24 @@ export class AuthController {
token(@Req() req): Promise<LoginDto> {
return this.authService.validateClaim(req.user)
}

@Post('nvmApiKey')
@ApiOperation({
description: 'Login using a sessionKey',
summary: 'Public',
})
@ApiResponse({
status: 201,
description: 'The access_token',
type: LoginDto,
})
@ApiResponse({
status: 401,
description: 'Unauthorized access',
})
@UseGuards(SessionKeyAuthGuard)
@Public()
sessionKey(@Req() req): Promise<LoginDto> {
return this.authService.validateClaim(req.payload)
}
}
2 changes: 2 additions & 0 deletions src/auth/auth.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ import { ConfigModule } from '../shared/config/config.module'
import { ConfigService } from '../shared/config/config.service'
import { NeverminedModule } from '../shared/nevermined/nvm.module'
import { NeverminedStrategy } from './nvm.strategy'
import { BackendModule } from '../shared/backend/backend.module'

@Module({
imports: [
ConfigModule,
BackendModule,
NeverminedModule,
PassportModule,
JwtModule.registerAsync({
Expand Down
34 changes: 34 additions & 0 deletions src/common/guards/auth/session-key.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { CanActivate, ExecutionContext, Injectable, Logger } from '@nestjs/common'
import { Request } from 'express'
import { ClientAssertion, parseJwt } from '../../helpers/jwt.utils'
import { BackendService } from '../../../shared/backend/backend.service'

@Injectable()
export class SessionKeyAuthGuard implements CanActivate {
constructor(private backendService: BackendService) {}

async canActivate(context: ExecutionContext) {
try {
const request = context.switchToHttp().getRequest()
const req = context.switchToHttp().getRequest<Request<unknown>>()
const clientAssertion: ClientAssertion = req.body
const payload = await parseJwt(clientAssertion.client_assertion)
request.payload = payload
if (!clientAssertion.nvm_key_hash) {
throw new Error('Invalid NVM API Key')
}

const nvmApiKey = await this.backendService.validateApiKey(clientAssertion.nvm_key_hash)
if (!nvmApiKey) {
throw new Error('Invalid NVM API Key')
}
if (nvmApiKey.userWallet !== payload.iss) {
throw new Error('Invalid NVM API Key')
}
return true
} catch (err: unknown) {
Logger.error(err)
return false
}
}
}
69 changes: 69 additions & 0 deletions src/common/helpers/jwt.utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { Logger } from '@nestjs/common'
import * as jose from 'jose'
import { getChecksumAddress, isValidAddress } from '@nevermined-io/sdk'

export interface ClientAssertion {
client_assertion_type: string
client_assertion: string
nvm_key_hash?: string
}

export interface Eip712Data {
message: string
chainId: number
}

export const CLIENT_ASSERTION_TYPE = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'

export type JWTPayload = jose.JWTPayload
export class JwtEthVerifyError extends Error {}

export const parseJwt = async (jwt: string): Promise<JWTPayload> => {
const { length } = jwt.split('.')

if (length !== 3) {
Logger.error('Invalid Compact JWS')
throw new JwtEthVerifyError('Invalid Compact JWS')
}

// decode and validate protected header
let parsedProtectedHeader: jose.ProtectedHeaderParameters
try {
parsedProtectedHeader = jose.decodeProtectedHeader(jwt)
} catch (error) {
Logger.error(`ProtectedHeader: Failed to decode header (${(error as Error).message})`)
throw new JwtEthVerifyError(
`ProtectedHeader: Failed to decode header (${(error as Error).message})`,
)
}
if (parsedProtectedHeader.alg !== 'ES256K') {
Logger.error('ProtectedHeader: Invalid algorithm')
throw new JwtEthVerifyError('ProtectedHeader: Invalid algorithm')
}

// verify the payload
let parsedPayload: JWTPayload
try {
parsedPayload = jose.decodeJwt(jwt)
} catch (error) {
Logger.error(`Payload: Failed to decode payload (${(error as Error).message})`)
throw new JwtEthVerifyError(`Payload: Failed to decode payload (${(error as Error).message})`)
}
if (!parsedPayload.iss) {
Logger.error('Payload: "iss" field is required')
throw new JwtEthVerifyError('Payload: "iss" field is required')
}

const isValid = isValidAddress(parsedPayload.iss)
if (!isValid) {
Logger.error('Payload: "iss" field must be a valid ethereum address')
throw new JwtEthVerifyError('Payload: "iss" field must be a valid ethereum address')
}
const isChecksumAddress = getChecksumAddress(parsedPayload.iss) === parsedPayload.iss
if (!isChecksumAddress) {
Logger.error('Payload: "iss" field must be a checksum address')
throw new JwtEthVerifyError('Payload: "iss" field must be a checksum address')
}

return parsedPayload
}
27 changes: 27 additions & 0 deletions src/shared/backend/backend.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { InternalServerErrorException } from '@nestjs/common'
import { ConfigService } from '../config/config.service'
import { firstValueFrom } from 'rxjs'
import { HttpModuleOptions, HttpService } from '@nestjs/axios'
import { NvmApiKey } from '@nevermined-io/sdk'

export interface AssetTransaction {
assetDid: string
Expand Down Expand Up @@ -117,4 +118,30 @@ export class BackendService {

return true
}

public async validateApiKey(apiKeyHash: string): Promise<NvmApiKey> {
if (!this.isNVMBackendEnabled) {
Logger.warn('Backend is disabled by config')
throw new InternalServerErrorException('Backend is disabled by config')
}

const requestConfig: HttpModuleOptions = {
url: `${this.backendUrl}/api/v1/api-keys/validate`,
method: 'POST',
headers: {
Authorization: `Bearer ${apiKeyHash}`,
'Content-Type': 'application/json',
},
}

const response = await firstValueFrom(this.httpService.request(requestConfig))
const obj = response.data

if (obj.error) {
Logger.error('Backend returned an error message:', obj.error)
throw new InternalServerErrorException(obj.error)
}

return obj
}
}
34 changes: 20 additions & 14 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1435,17 +1435,17 @@
jose "^4.11.2"
passport-strategy "^1.0.0"

"@nevermined-io/[email protected].14":
version "3.0.14"
resolved "https://registry.yarnpkg.com/@nevermined-io/sdk/-/sdk-3.0.14.tgz#9391c18347779d5ab883cc876e3224a641c6367b"
integrity sha512-CUMU0d8OoydI6573OET6jgZIhlGXi4oy7jCUhVUuP+er1KZ2FbG88wmtT0UqzOFJo4TJJ7+YDmVEHYel0fjQ+w==
"@nevermined-io/[email protected].24":
version "3.0.24"
resolved "https://registry.yarnpkg.com/@nevermined-io/sdk/-/sdk-3.0.24.tgz#42e45cd113411aa94c33c50525c8d3929fbb6c1c"
integrity sha512-0Pf0l6vZ23gCRKfbgSJkq8c+xRd41s3n4wM74N9SdCTeb9etnUZW38Kq/wn1wKjFnu+Rykt/r/wp/9ZRXH74GQ==
dependencies:
"@alchemy/aa-core" "3.12.1"
"@apollo/client" "^3.7.16"
"@turnkey/viem" "0.4.16"
"@zerodev/ecdsa-validator" "5.2.3"
"@zerodev/sdk" "5.2.11"
"@zerodev/session-key" "^5.3.0"
"@zerodev/session-key" "5.3.0"
assert "^2.0.0"
cross-fetch "^4.0.0"
crypto-browserify "^3.12.0"
Expand All @@ -1469,6 +1469,7 @@
url "^0.11.0"
uuid "^9.0.1"
viem "2.9.31"
vm-browserify "^1.1.2"
whatwg-url "^14.0.0"

"@next/[email protected]":
Expand Down Expand Up @@ -2409,7 +2410,7 @@
dependencies:
semver "^7.6.0"

"@zerodev/session-key@^5.3.0":
"@zerodev/[email protected]":
version "5.3.0"
resolved "https://registry.yarnpkg.com/@zerodev/session-key/-/session-key-5.3.0.tgz#d347134a359d06ec03f6bb8378345d5d2d3f4c22"
integrity sha512-HZAFuhUiiG2bx8fqBW1LujH7Rn8zdbraipOoFXh55tDTyA3GX6M4RgiqzZPRoHMYfCYImE6ETBj0a97PjsBRHQ==
Expand Down Expand Up @@ -4681,17 +4682,17 @@ [email protected], ethers@^5.6.5:
"@ethersproject/wordlists" "5.7.0"

ethers@^6.12.1:
version "6.12.1"
resolved "https://registry.yarnpkg.com/ethers/-/ethers-6.12.1.tgz#517ff6d66d4fd5433e38e903051da3e57c87ff37"
integrity sha512-j6wcVoZf06nqEcBbDWkKg8Fp895SS96dSnTCjiXT+8vt2o02raTn4Lo9ERUuIVU5bAjoPYeA+7ytQFexFmLuVw==
version "6.13.2"
resolved "https://registry.yarnpkg.com/ethers/-/ethers-6.13.2.tgz#4b67d4b49e69b59893931a032560999e5e4419fe"
integrity sha512-9VkriTTed+/27BGuY1s0hf441kqwHJ1wtN2edksEtiRvXx+soxRX3iSXTfFqq2+YwrOqbDoTHjIhQnjJRlzKmg==
dependencies:
"@adraffy/ens-normalize" "1.10.1"
"@noble/curves" "1.2.0"
"@noble/hashes" "1.3.2"
"@types/node" "18.15.13"
aes-js "4.0.0-beta.5"
tslib "2.4.0"
ws "8.5.0"
ws "8.17.1"

[email protected]:
version "0.1.6"
Expand Down Expand Up @@ -9402,6 +9403,11 @@ [email protected]:
isows "1.0.3"
ws "8.13.0"

vm-browserify@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==

walker@^1.0.8:
version "1.0.8"
resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f"
Expand Down Expand Up @@ -9646,10 +9652,10 @@ [email protected]:
resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0"
integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==

ws@8.5.0:
version "8.5.0"
resolved "https://registry.yarnpkg.com/ws/-/ws-8.5.0.tgz#bfb4be96600757fe5382de12c670dab984a1ed4f"
integrity sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==
ws@8.17.1:
version "8.17.1"
resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b"
integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==

[email protected]:
version "0.6.2"
Expand Down

0 comments on commit dfbb693

Please sign in to comment.