Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
9425566
routes and unit tests
giurgiur99 Jun 3, 2025
fec1996
unit and integration tests
giurgiur99 Jun 4, 2025
9664a3b
add jwt
giurgiur99 Jun 4, 2025
a41cc02
add jwt types
giurgiur99 Jun 4, 2025
85d1a5f
fix unit tests
giurgiur99 Jun 4, 2025
7ac6c68
refactor to handler
giurgiur99 Jun 4, 2025
99d99b8
remove unused typesense schema
giurgiur99 Jun 4, 2025
c3451a8
remove console logs
giurgiur99 Jun 4, 2025
02611ca
revert ddo file
giurgiur99 Jun 4, 2025
3fce736
refactor
giurgiur99 Jun 4, 2025
b131405
use port 8000 tests
giurgiur99 Jun 4, 2025
d21dfdf
test with handler
giurgiur99 Jun 4, 2025
36d2cd1
revert aquarius
giurgiur99 Jun 4, 2025
a562545
decorator used for valdiation
giurgiur99 Jun 4, 2025
b9d0ffa
fix validation tests
giurgiur99 Jun 4, 2025
e4d18eb
support for new node instance
giurgiur99 Jun 4, 2025
5c0c0e4
force refresh node
giurgiur99 Jun 4, 2025
2e891da
new instance test
giurgiur99 Jun 5, 2025
8a4a997
fix bad enum
giurgiur99 Jun 5, 2025
0fe8395
force refresh handler
giurgiur99 Jun 5, 2025
c262457
cli tests paid compute
giurgiur99 Jun 5, 2025
eb1483c
revert main cli
giurgiur99 Jun 5, 2025
1c00310
specify message in handler
giurgiur99 Jun 5, 2025
54926c2
remove duplicate routes
giurgiur99 Jun 5, 2025
f0d2d66
implement skipValidation params
giurgiur99 Jun 5, 2025
7120eb1
override env tests
giurgiur99 Jun 5, 2025
51f9896
Merge branch 'main' into feature/auth-token
giurgiur99 Jun 6, 2025
38e0101
add header in handler
giurgiur99 Jun 6, 2025
987d194
add docker comput envs in ci
giurgiur99 Jun 6, 2025
843b029
Merge branch 'feature/auth-token' of https://github.com/oceanprotocol…
giurgiur99 Jun 6, 2025
6bfb248
reorder priority
giurgiur99 Jun 10, 2025
92e003a
add nonce
giurgiur99 Jun 10, 2025
42bc3f3
Merge branch 'main' into feature/auth-token
giurgiur99 Jun 10, 2025
d9c44c1
check nonce
giurgiur99 Jun 11, 2025
797a066
name routes
giurgiur99 Jun 11, 2025
3476f90
env example and use config
giurgiur99 Jun 11, 2025
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
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export MAX_CHECKSUM_LENGTH=
export LOG_LEVEL=
export HTTP_API_PORT=
export VALIDATE_UNSIGNED_DDO=
export JWT_SECRET=

## p2p

Expand Down
1 change: 1 addition & 0 deletions docs/env.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Environmental variables are also tracked in `ENVIRONMENT_VARIABLES` within `src/
- `AUTHORIZED_PUBLISHERS`: Authorized list of publishers. If present, Node will only index assets published by the accounts in the list. Example: `"[\"0x967da4048cD07aB37855c090aAF366e4ce1b9F48\",\"0x388C818CA8B9251b393131C08a736A67ccB19297\"]"`
- `AUTHORIZED_PUBLISHERS_LIST`: AccessList contract addresses (per chain). If present, Node will only index assets published by the accounts present on the given access lists. Example: `"{ \"8996\": [\"0x967da4048cD07aB37855c090aAF366e4ce1b9F48\",\"0x388C818CA8B9251b393131C08a736A67ccB19297\"] }"`
- `VALIDATE_UNSIGNED_DDO`: If set to `false`, the node will not validate unsigned DDOs and will request a signed message with the publisher address, nonce and signature. Default is `true`. Example: `false`
- `JWT_SECRET`: Secret used to sign JWT tokens. Default is `ocean-node-secret`. Example: `"my-secret-jwt-token"`

## Payments

Expand Down
84 changes: 82 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
"hyperdiff": "^2.0.16",
"ip": "^2.0.1",
"it-pipe": "^3.0.1",
"jsonwebtoken": "^9.0.2",
"libp2p": "^1.8.0",
"lodash.clonedeep": "^4.5.0",
"lzma-purejs-requirejs": "^1.0.0",
Expand All @@ -112,6 +113,7 @@
"@types/dockerode": "^3.3.31",
"@types/express": "^4.17.17",
"@types/ip": "^1.1.3",
"@types/jsonwebtoken": "^9.0.9",
"@types/lzma-native": "^4.0.4",
"@types/mocha": "^10.0.10",
"@types/node": "^20.14.2",
Expand Down
1 change: 1 addition & 0 deletions src/@types/OceanNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ export interface OceanNodeConfig {
unsafeURLs?: string[]
isBootstrap?: boolean
validateUnsignedDDO?: boolean
jwtSecret?: string
}

export interface P2PStatusResponse {
Expand Down
14 changes: 14 additions & 0 deletions src/@types/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
export interface Command {
command: string // command name
node?: string // if not present it means current node
authorization?: string
}

export interface GetP2PPeerCommand extends Command {
Expand Down Expand Up @@ -82,6 +83,7 @@ export interface ValidateDDOCommand extends Command {
publisherAddress?: string
nonce?: string
signature?: string
message?: string
}

export interface StatusCommand extends Command {
Expand Down Expand Up @@ -261,3 +263,15 @@ export interface StartStopIndexingCommand extends AdminCommand {
export interface PolicyServerPassthroughCommand extends Command {
policyServerPassthrough?: any
}

export interface CreateAuthTokenCommand extends Command {
address: string
signature: string
validUntil?: number | null
}

export interface InvalidateAuthTokenCommand extends Command {
address: string
signature: string
token: string
}
14 changes: 12 additions & 2 deletions src/OceanNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { pipe } from 'it-pipe'
import { GENERIC_EMOJIS, LOG_LEVELS_STR } from './utils/logging/Logger.js'
import { BaseHandler } from './components/core/handler/handler.js'
import { C2DEngines } from './components/c2d/compute_engines.js'
import { Auth } from './components/Auth/index.js'

export interface RequestLimiter {
requester: string | string[] // IP address or peer ID
Expand All @@ -35,6 +36,7 @@ export class OceanNode {
// requester
private remoteCaller: string | string[]
private requestMap: Map<string, RequestLimiter>
private auth: Auth

// eslint-disable-next-line no-useless-constructor
private constructor(
Expand All @@ -47,6 +49,9 @@ export class OceanNode {
this.coreHandlers = CoreHandlersRegistry.getInstance(this)
this.requestMap = new Map<string, RequestLimiter>()
this.config = config
if (this.db && this.db?.authToken) {
this.auth = new Auth(this.db.authToken)
}
if (node) {
node.setCoreHandlers(this.coreHandlers)
}
Expand All @@ -64,9 +69,10 @@ export class OceanNode {
db?: Database,
node?: OceanP2P,
provider?: OceanProvider,
indexer?: OceanIndexer
indexer?: OceanIndexer,
newInstance: boolean = false
): OceanNode {
if (!OceanNode.instance) {
if (!OceanNode.instance || newInstance) {
// prepare compute engines
this.instance = new OceanNode(config, db, node, provider, indexer)
}
Expand Down Expand Up @@ -136,6 +142,10 @@ export class OceanNode {
return this.requestMap
}

public getAuth(): Auth {
return this.auth
}

/**
* Use this method to direct calls to the node as node cannot dial into itself
* @param message command message
Expand Down
120 changes: 120 additions & 0 deletions src/components/Auth/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { AuthToken, AuthTokenDatabase } from '../database/AuthTokenDatabase.js'
import jwt from 'jsonwebtoken'
import { checkNonce, NonceResponse } from '../core/utils/nonceHandler.js'
import { OceanNode } from '../../OceanNode.js'
import { getConfiguration } from '../../utils/index.js'

export interface CommonValidation {
valid: boolean
error: string
}

export class Auth {
private authTokenDatabase: AuthTokenDatabase

public constructor(authTokenDatabase: AuthTokenDatabase) {
this.authTokenDatabase = authTokenDatabase
}

public async getJwtSecret(): Promise<string> {
const config = await getConfiguration()
return config.jwtSecret
}

public getMessage(address: string, nonce: string): string {
return address + nonce
}

async getJWTToken(address: string, nonce: string, createdAt: number): Promise<string> {
const jwtToken = jwt.sign(
{
address,
nonce,
createdAt
},
await this.getJwtSecret()
)

return jwtToken
}

async insertToken(
address: string,
jwtToken: string,
validUntil: number,
createdAt: number
): Promise<void> {
await this.authTokenDatabase.createToken(jwtToken, address, validUntil, createdAt)
}

async invalidateToken(jwtToken: string): Promise<void> {
await this.authTokenDatabase.invalidateToken(jwtToken)
}

async validateToken(token: string): Promise<AuthToken | null> {
const tokenEntry = await this.authTokenDatabase.validateToken(token)
if (!tokenEntry) {
return null
}
return tokenEntry
}

/**
* Validates the authentication or token
* You need to provider either a token or an address, signature and message
* @param {string} token - The token to validate
* @param {string} address - The address to validate
* @param {string} signature - The signature to validate
* @param {string} message - The message to validate
* @returns The validation result
*/
async validateAuthenticationOrToken({
token,
address,
nonce,
signature
}: {
token?: string
address?: string
nonce?: string
signature?: string
}): Promise<CommonValidation> {
try {
if (signature && address && nonce) {
const oceanNode = OceanNode.getInstance()
const nonceCheckResult: NonceResponse = await checkNonce(
oceanNode.getDatabase().nonce,
address,
parseInt(nonce),
signature,
this.getMessage(address, nonce)
)

if (!nonceCheckResult.valid) {
return { valid: false, error: nonceCheckResult.error }
}

if (nonceCheckResult.valid) {
return { valid: true, error: '' }
}
}

if (token) {
const authToken = await this.validateToken(token)
if (authToken) {
return { valid: true, error: '' }
}

return { valid: false, error: 'Invalid token' }
}

return {
valid: false,
error:
'Invalid authentication, you need to provide either a token or an address, signature, message and nonce'
}
} catch (e) {
return { valid: false, error: `Error during authentication validation: ${e}` }
}
}
}
Loading
Loading