Skip to content

Commit

Permalink
feat: Add authentication
Browse files Browse the repository at this point in the history
  • Loading branch information
DaevMithran committed May 9, 2023
1 parent aea3272 commit b5f912f
Show file tree
Hide file tree
Showing 13 changed files with 852 additions and 506 deletions.
963 changes: 512 additions & 451 deletions package-lock.json

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@
"README.md"
],
"dependencies": {
"@cheqd/did-provider-cheqd": "^3.1.0-develop.1",
"@cheqd/did-provider-cheqd": "^3.1.1",
"@cosmjs/amino": "^0.30.1",
"@cosmjs/encoding": "^0.30.1",
"@veramo/core": "^5.1.2",
"@veramo/credential-ld": "4.3.0",
"@veramo/credential-w3c": "^5.1.2",
Expand All @@ -43,11 +45,13 @@
"did-resolver": "^4.0.1",
"dotenv": "^16.0.3",
"express": "^4.18.2",
"express-jwt": "^8.4.1",
"express-validator": "^6.15.0",
"helmet": "^6.0.1",
"node-cache": "^5.1.2",
"pg": "^8.10.0",
"pg-connection-string": "^2.5.0",
"secp256k1": "^5.0.0",
"swagger-ui-express": "^4.6.1",
"typeorm": "^0.3.15"
},
Expand All @@ -64,6 +68,7 @@
"@types/helmet": "^4.0.0",
"@types/jest": "^29.2.5",
"@types/node": "^18.11.18",
"@types/secp256k1": "^4.0.3",
"@types/swagger-ui-express": "^4.1.3",
"@types/uuid": "^9.0.0",
"@typescript-eslint/eslint-plugin": "^5.48.0",
Expand Down
16 changes: 13 additions & 3 deletions src/app.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import express from 'express'
import express, { NextFunction, Request, Response } from 'express'
import Helmet from 'helmet'
import { CredentialController } from './controllers/credentials'
import { StoreController } from './controllers/store'
Expand All @@ -8,6 +8,8 @@ import * as swagger from 'swagger-ui-express'
import * as swaggerJson from '../swagger.json'
import { IssuerController } from './controllers/issuer'
import { Connection } from './database/connection/connection'
import { CustomerController } from './controllers/customer'
import { Authentication } from './middleware/authentication'

require('dotenv').config()

Expand All @@ -18,7 +20,7 @@ class App {
this.express = express()
this.middleware()
this.routes()
// Connection.instance.connect()
Connection.instance.connect()
}

private middleware() {
Expand All @@ -36,6 +38,9 @@ class App {
}
}))
this.express.use('/api-docs', swagger.serve, swagger.setup(swaggerJson))
this.express.use(Authentication.expressJWT)
this.express.use(Authentication.authenticate)
this.express.use(Authentication.handleError)
}

private routes() {
Expand All @@ -59,8 +64,13 @@ class App {
app.get(`${URL_PREFIX}/did`, new IssuerController().getDids)
app.get(`${URL_PREFIX}/did/:did`, new IssuerController().getDids)

// customer
app.post(`${URL_PREFIX}/customer`, new CustomerController().create)
app.put(`${URL_PREFIX}/customer`, new CustomerController().update)
app.get(`${URL_PREFIX}/customer`, new CustomerController().get)

// 404 for all other requests
app.all('*', (req, res) => res.status(400).send('Bad request'))
app.all('*', (req, res) => res.status(400).send('Bad requestssss'))
}

}
Expand Down
13 changes: 8 additions & 5 deletions src/controllers/credentials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { Request, Response } from 'express'

import { Credentials } from '../services/credentials'
import { check, validationResult } from 'express-validator'
import { Identity } from '../services/identity'
import { CustomerService } from '../services/customer'

export class CredentialController {

Expand Down Expand Up @@ -30,8 +30,12 @@ export class CredentialController {
return response.status(400).json({ error: result.array()[0].msg })
}
try {
await Identity.instance.getDid(request.body.issuerDid)
response.json(await Credentials.instance.issue_credential(request.body))
if (!await CustomerService.instance.find(response.locals.customerId, {did: request.body.issuerDid})) {
response.status(400).json({
error: `Issuer DID ${request.body.issuerDid} not found`
})
}
response.status(200).json(await Credentials.instance.issue_credential(request.body))
} catch (error) {
response.status(500).json({
error: `Internal error: ${error}`
Expand All @@ -49,12 +53,11 @@ export class CredentialController {
return response.status(400).json({ error: result.array()[0].msg })
}
try {
return response.json(await Credentials.instance.verify_credentials(request.body.credential))
return response.status(200).json(await Credentials.instance.verify_credentials(request.body.credential))
} catch (error) {
response.status(500).json({
error: `Internal error: ${error}`
})
}
}

}
48 changes: 48 additions & 0 deletions src/controllers/customer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import type { Request, Response } from 'express'

import { CustomerService } from '../services/customer'
import { Identity } from '../services/identity'

export class CustomerController {

public async create(request: Request, response: Response) {
try {
const kid = (await Identity.instance.createKey('Secp256k1')).kid
const customer = await CustomerService.instance.create(response.locals.customerId, kid)
return response.status(200).json(customer)
} catch (error) {
return response.status(500).json({
Error: `Error creating customer ${error}`
})
}
}

public async update(request: Request, response: Response) {
try {
const result = await CustomerService.instance.update(response.locals.customerId, request.body)
return response.status(200).json(result)
} catch (error) {
return response.status(500).json({
Error: error
})
}
}

public async get(request: Request, response: Response) {
try {
const result = await CustomerService.instance.get(response.locals.customerId)
if(result && !Array.isArray(result)) {
const { account, ...res } = result
return response.status(200).json(res)
}

return response.status(400).json({
error: 'Customer not found'
})
} catch (error) {
return response.status(500).json({
Error: error
})
}
}
}
40 changes: 37 additions & 3 deletions src/controllers/issuer.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import { Request, Response } from 'express'
import { Identity } from '../services/identity'
import { CustomerService } from '../services/customer'
import { generateDidDoc, validateSpecCompliantPayload } from '../helpers/helpers'
import { DIDDocument } from 'did-resolver'
import { v4 } from 'uuid'
import { MethodSpecificIdAlgo } from '@cheqd/sdk'
import { CustomerEntity } from '../database/entities/customer.entity'

export class IssuerController {

public async createKey(request: Request, response: Response) {
try {
const key = await Identity.instance.createKey()
await CustomerService.instance.update(response.locals.customerId, { kids: [key.kid] })
return response.status(200).json(key)
} catch (error) {
return response.status(500).json({
Expand All @@ -16,6 +23,10 @@ export class IssuerController {

public async getKey(request: Request, response: Response) {
try {
const isOwner = await CustomerService.instance.find(response.locals.customerId, {kid: request.params.kid})
if(!isOwner) {
return response.status(401).json(`Not found`)
}
const key = await Identity.instance.getKey(request.params.kid)
return response.status(200).json(key)
} catch (error) {
Expand All @@ -26,9 +37,32 @@ export class IssuerController {
}

public async createDid(request: Request, response: Response) {
const { network, kids, didDocument, alias } = request.body
const { options, secret, alias } = request.body
const { methodSpecificIdAlgo, network, versionId = v4()} = options
const verificationMethod = secret?.verificationMethod
let didDocument: DIDDocument
let kids: string[] = []
try {
const did = await Identity.instance.createDid(network, didDocument, alias)
if (options.didDocument && validateSpecCompliantPayload(options.didDocument)) {
didDocument = options.didDocument
} else if (verificationMethod) {
const key = await Identity.instance.createKey()
kids.push(key.kid)
didDocument = generateDidDoc({
verificationMethod: verificationMethod.type,
verificationMethodId: verificationMethod.id || 'key-1',
methodSpecificIdAlgo: (methodSpecificIdAlgo as MethodSpecificIdAlgo) || MethodSpecificIdAlgo.Uuid,
network,
publicKey: key.publicKeyHex
})
} else {
return response.status(400).json({
error: 'Provide a DID Document or atleast one verification method'
})
}
const customer = await CustomerService.instance.get(response.locals.customerId) as CustomerEntity
const did = await Identity.instance.createDid(network, didDocument, alias, customer.account)
await CustomerService.instance.update(response.locals.customerId, { kids, dids: [did.did] })
return response.status(200).json(did)
} catch (error) {
return response.status(500).json({
Expand All @@ -43,7 +77,7 @@ export class IssuerController {
if(request.params.did) {
did = await Identity.instance.resolveDid(request.params.did)
} else {
did = await Identity.instance.listDids()
did = await CustomerService.instance.get(response.locals.customerId)
}

return response.status(200).json(did)
Expand Down
3 changes: 2 additions & 1 deletion src/database/connection/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { DataSource } from 'typeorm'
import { migrations, Entities } from '@veramo/data-store'
import { CustomerEntity } from '../entities/customer.entity'

require('dotenv').config()

const { ISSUER_DATABASE_URL} = process.env

export class Connection {
Expand All @@ -25,7 +27,6 @@ export class Connection {
ssl: config.ssl ? true : false,
migrations,
entities: [...Entities, CustomerEntity],
synchronize: true,
logging: ['error', 'info', 'warn']
})
}
Expand Down
41 changes: 25 additions & 16 deletions src/database/entities/customer.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,35 @@ import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'

@Entity('customers')
export class CustomerEntity {
@Column('string')
customerId
@PrimaryGeneratedColumn('uuid')
customerId!: string

@Column('string', {array: true})
kids
@Column('text')
account!: string

@Column('string', {array: true})
dids: string[]
@Column('text')
address!: string

@Column('text', {array: true, default: []})
kids!: string[]

@Column('string', {array: true})
claimIds: string[]
@Column('text', {array: true, default: []})
dids!: string[]

@Column('string', {array: true})
presentationIds: string[]
@Column('text', {array: true, default: []})
claimIds!: string[]

constructor({ customerId, kids=[], dids=[], claimIds=[], presentationIds=[] }: { customerId: string, kids?: string[], dids?: string[], claimIds?: string[], presentationIds?: string[]}) {
this.customerId = customerId
this.kids = kids
this.dids = dids
this.claimIds = claimIds
this.presentationIds = presentationIds
@Column('text', {array: true, default: []})
presentationIds!: string[]


constructor(customerId: string, account: string, address: string) {
this.customerId= customerId
this.account = account
this.address = address
this.kids= []
this.dids= []
this.claimIds= []
this.presentationIds= []
}
}
73 changes: 73 additions & 0 deletions src/helpers/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import type { DIDDocument } from 'did-resolver'
import type { MethodSpecificIdAlgo, CheqdNetwork } from '@cheqd/sdk'

import { VerificationMethods, createVerificationKeys, createDidVerificationMethod, createDidPayload } from '@cheqd/sdk'
import { SpecValidationResult } from '../types/types'

import { fromString } from 'uint8arrays'
import { rawSecp256k1PubkeyToRawAddress } from '@cosmjs/amino'
import { toBech32 } from '@cosmjs/encoding'
import { publicKeyConvert } from 'secp256k1'

export function validateSpecCompliantPayload(didDocument: DIDDocument): SpecValidationResult {
// id is required, validated on both compile and runtime
if (!didDocument.id && !didDocument.id.startsWith('did:cheqd:')) return { valid: false, error: 'id is required' }

// verificationMethod is required
if (!didDocument.verificationMethod) return { valid: false, error: 'verificationMethod is required' }

// verificationMethod must be an array
if (!Array.isArray(didDocument.verificationMethod))
return { valid: false, error: 'verificationMethod must be an array' }

// verificationMethod must be not be empty
if (!didDocument.verificationMethod.length) return { valid: false, error: 'verificationMethod must be not be empty' }

// verificationMethod types must be supported
const isValidVerificationMethod = didDocument.verificationMethod.every((vm) => {
switch (vm.type) {
case VerificationMethods.Ed255192020:
return vm.publicKeyMultibase != null
case VerificationMethods.JWK:
return vm.publicKeyJwk != null
case VerificationMethods.Ed255192018:
return vm.publicKeyBase58 != null
default:
return false
}
})

if (!isValidVerificationMethod) return { valid: false, error: 'verificationMethod publicKey is Invalid' }

const isValidService = didDocument.service
? didDocument?.service?.every((s) => {
return s?.serviceEndpoint && s?.id && s?.type
})
: true

if (!isValidService) return { valid: false, error: 'Service is Invalid' }
return { valid: true } as SpecValidationResult
}

export function generateDidDoc(options: IDidDocOptions) {
const { verificationMethod, methodSpecificIdAlgo, verificationMethodId, network, publicKey } = options
const verificationKeys = createVerificationKeys(publicKey, methodSpecificIdAlgo, verificationMethodId, network)
if (!verificationKeys) {
throw new Error('Invalid DID options')
}
const verificationMethods = createDidVerificationMethod([verificationMethod], [verificationKeys])

return createDidPayload(verificationMethods, [verificationKeys])
}

export function getCosmosAccount(kid: string) {
return toBech32('cheqd', rawSecp256k1PubkeyToRawAddress(publicKeyConvert(fromString(kid, 'hex'), true)))
}

export interface IDidDocOptions {
verificationMethod: VerificationMethods
verificationMethodId: any
methodSpecificIdAlgo: MethodSpecificIdAlgo
network: CheqdNetwork
publicKey: string
}
Loading

0 comments on commit b5f912f

Please sign in to comment.