Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add auth toggle && missing api's #234

Merged
merged 2 commits into from
May 30, 2023
Merged
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
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,8 @@ OIDC_ISSUER='http://localhost:3001/oidc'

# Root endpoint for credential service backend
AUDIENCE_ENDPOINT='http://localhost:8787/1.0/api/'


# Authentication
ENABLE_AUTH="boolean,default:false"
CUSTOMER_ID="default customer id"
3 changes: 2 additions & 1 deletion src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,10 @@ class App {
// issuer
app.post(`/key/create`, new IssuerController().createKey)
app.get(`/key/:kid`, new IssuerController().getKey)
app.post(`/did/create`, new IssuerController().createDid)
app.post(`/did/create`, IssuerController.didValidator, new IssuerController().createDid)
app.get(`/did/list`, new IssuerController().getDids)
app.get(`/did/:did`, new IssuerController().getDids)
app.post(`/:did/create-resource`, IssuerController.resourceValidator, new IssuerController().createResource)

// customer
app.post(`/account`, new CustomerController().create)
Expand Down
95 changes: 93 additions & 2 deletions src/controllers/issuer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,49 @@ import type { Request, Response } from 'express'

import { DIDDocument } from 'did-resolver'
import { v4 } from 'uuid'
import { MethodSpecificIdAlgo } from '@cheqd/sdk'
import { MethodSpecificIdAlgo, VerificationMethods, CheqdNetwork } from '@cheqd/sdk'
import { MsgCreateResourcePayload } from '@cheqd/ts-proto/cheqd/resource/v2/index.js'

import { Identity } from '../services/identity.js'
import { CustomerService } from '../services/customer.js'
import { generateDidDoc, validateSpecCompliantPayload } from '../helpers/helpers.js'
import { CustomerEntity } from '../database/entities/customer.entity.js'
import { check, param, validationResult } from 'express-validator'
import { fromString } from 'uint8arrays'

export class IssuerController {

public static didValidator = [
check('didDocument').optional().isArray().custom((value)=>{
const { valid } = validateSpecCompliantPayload(value)
return valid
}).withMessage('Invalid didDocument'),
check('secret.verificationMethod.type')
.optional()
.isString()
.isIn([VerificationMethods.Ed255192020, VerificationMethods.Ed255192018, VerificationMethods.JWK])
.withMessage('Invalid verificationMethod'),
check('secret.verificationMethod.id')
.optional()
.isString()
.withMessage('Invalid verificationMethod'),
check('options.methodSpecificIdAlgo').optional().isString().isIn([MethodSpecificIdAlgo.Base58, MethodSpecificIdAlgo.Uuid]).withMessage('Invalid methodSpecificIdAlgo'),
check('options.network').optional().isString().isIn([CheqdNetwork.Mainnet, CheqdNetwork.Testnet]).withMessage('Invalid network'),
]

public static resourceValidator = [
param('did').exists().isString().contains('did:cheqd').withMessage('Invalid DID'),
check('jobId').custom((value, {req})=>{
if(!value && !(req.body.name && req.body.type && req.body.data)) return false
return true
}).withMessage('name, type and data are required'),
check('name').optional().isString().withMessage('Invalid name'),
check('type').optional().isString().withMessage('Invalid type'),
check('data').optional().isString().withMessage('Invalid data'),
check('alsoKnownAs').optional().isArray().withMessage('Invalid alsoKnownAs'),
check('alsoKnownAs.*.uri').isString().withMessage('Invalid uri'),
check('alsoKnownAs.*.description').isString().withMessage('Invalid description')
]

public async createKey(request: Request, response: Response) {
try {
Expand Down Expand Up @@ -39,13 +74,20 @@ export class IssuerController {
}

public async createDid(request: Request, response: Response) {
const result = validationResult(request)
if (!result.isEmpty()) {
return response.status(400).json({
error: result.array()[0].msg
})
}

const { options, secret } = request.body
const { methodSpecificIdAlgo, network, versionId = v4()} = options
const verificationMethod = secret?.verificationMethod
let didDocument: DIDDocument
let kids: string[] = []
try {
if (options.didDocument && validateSpecCompliantPayload(options.didDocument)) {
if (options.didDocument) {
didDocument = options.didDocument
} else if (verificationMethod) {
const key = await Identity.instance.createKey()
Expand Down Expand Up @@ -74,6 +116,55 @@ export class IssuerController {
}
}

public async createResource(request: Request, response: Response) {
const result = validationResult(request)
if (!result.isEmpty()) {
return response.status(400).json({
error: result.array()[0].msg
})
}

const { did } = request.params
let { data, name, type, alsoKnownAs, version } = request.body

let resourcePayload: Partial<MsgCreateResourcePayload> = {}
try {
// check if did is registered on the ledger
let resolvedDocument = await Identity.instance.resolveDid(did)
if(!resolvedDocument?.didDocument || resolvedDocument.didDocumentMetadata.deactivated) {
return response.status(400).send({
error: `${did} is a Deactivated DID`
})
} else {
resolvedDocument = resolvedDocument.didDocument
}

resourcePayload = {
collectionId: did.split(':').pop()!,
id: v4(),
name,
resourceType: type,
data: fromString(data, 'base64'),
version,
alsoKnownAs
}
const result = await Identity.instance.createResource(resourcePayload, response.locals.customerId)
if ( result ) {
return response.status(201).json({
resource: resourcePayload
})
} else {
return response.status(500).json({
error: 'Error creating resource'
})
}
} catch (error) {
return response.status(500).json({
error: `${error}`
})
}
}

public async getDids(request: Request, response: Response) {
try {
let did: any
Expand Down
42 changes: 25 additions & 17 deletions src/middleware/authentication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import { IncomingHttpHeaders } from 'http';
import * as dotenv from 'dotenv'
dotenv.config()

const { OIDC_JWKS_ENDPOINT, AUDIENCE_ENDPOINT, OIDC_ISSUER } = process.env
const bearerTokenIdentifier = 'Bearer';
const { OIDC_JWKS_ENDPOINT, AUDIENCE_ENDPOINT, OIDC_ISSUER, ENABLE_AUTH, CUSTOMER_ID } = process.env
const bearerTokenIdentifier = 'Bearer'

export const extractBearerTokenFromHeaders = ({ authorization }: IncomingHttpHeaders) => {
if (!authorization) {
Expand Down Expand Up @@ -58,21 +58,29 @@ export class Authentication {
if (jwtRequest.path == '/' || jwtRequest.path == '/swagger') return next()

try {
const token = extractBearerTokenFromHeaders(jwtRequest.headers)

const { payload } = await jwtVerify(
token, // The raw Bearer Token extracted from the request header
createRemoteJWKSet(new URL(OIDC_JWKS_ENDPOINT)), // generate a jwks using jwks_uri inquired from Logto server
{
// expected issuer of the token, should be issued by the Logto server
issuer: OIDC_ISSUER,
// expected audience token, should be the resource indicator of the current API
audience: AUDIENCE_ENDPOINT,
}
);

// custom payload logic
response.locals.customerId = payload.sub
if (ENABLE_AUTH) {
const token = extractBearerTokenFromHeaders(jwtRequest.headers)

const { payload } = await jwtVerify(
token, // The raw Bearer Token extracted from the request header
createRemoteJWKSet(new URL(OIDC_JWKS_ENDPOINT)), // generate a jwks using jwks_uri inquired from Logto server
{
// expected issuer of the token, should be issued by the Logto server
issuer: OIDC_ISSUER,
// expected audience token, should be the resource indicator of the current API
audience: AUDIENCE_ENDPOINT,
}
);

// custom payload logic
response.locals.customerId = payload.sub
} else if (CUSTOMER_ID) {
response.locals.customerId = CUSTOMER_ID
} else {
return response.status(400).json({
error: `Unauthorized error`
})
}
next()
} catch (err) {
return response.status(500).send({
Expand Down
26 changes: 24 additions & 2 deletions src/services/identity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ import { DIDResolverPlugin } from '@veramo/did-resolver'
import { KeyManager } from '@veramo/key-manager'
import { KeyManagementSystem, SecretBox } from '@veramo/kms-local'
import { KeyStore, DIDStore, PrivateKeyStore } from '@veramo/data-store'
import { CredentialIssuerLD, LdDefaultContexts, VeramoEd25519Signature2018, VeramoEd25519Signature2020 } from '@veramo/credential-ld'
import { CheqdDIDProvider, getResolver as CheqdDidResolver } from '@cheqd/did-provider-cheqd'
import { CredentialIssuerLD, LdDefaultContexts, VeramoEd25519Signature2018 } from '@veramo/credential-ld'
import { CheqdDIDProvider, getResolver as CheqdDidResolver, ResourcePayload } from '@cheqd/did-provider-cheqd'
import { CheqdNetwork } from '@cheqd/sdk'
import { Resolver, ResolverRegistry } from 'did-resolver'
import { v4 } from 'uuid'
Expand Down Expand Up @@ -194,4 +194,26 @@ export class Identity {

return identifier
}

async createResource(resource: ResourcePayload, agentId?: string) {
try {
const agentService = agentId ? await this.create_agent(agentId) : this.agent
if (!agentService) throw new Error('No initialised agent found.')

const [kms] = await agentService.keyManagerGetKeyManagementSystems()

const result: boolean = await agentService.execute(
'cheqdCreateLinkedResource',
{
kms,
payload: {
data: resource
}
}
)
return result
} catch (error) {
throw new Error(`${error}`)
}
}
}
6 changes: 5 additions & 1 deletion src/types/environment.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ declare global {
interface ProcessEnv {
MAINNET_RPC_URL: string
TESTNET_RPC_URL: string
RESOLVER_URL: string
RESOLVER_URL: string
ALLOWED_ORIGINS: string | undefined
DB_CONNECTION_URL: string
DB_ENCRYPTION_KEY: string
Expand All @@ -20,6 +20,10 @@ declare global {
ISSUER_VERIDA_PRIVATE_KEY: string
POLYGON_PRIVATE_KEY: string
VERIDA_NETWORK: EnvironmentType

// auth
ENABLE_AUTH: boolean | undefined
CUSTOMER_ID: string | undefined
}
}
}
Expand Down