diff --git a/packages/headless-web3-provider/CHANGELOG.md b/packages/headless-web3-provider/CHANGELOG.md
new file mode 100644
index 000000000..a3ce99779
--- /dev/null
+++ b/packages/headless-web3-provider/CHANGELOG.md
@@ -0,0 +1,21 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+## [0.14.1](https://github.com/Sphereon-Opensource/SSI-SDK/compare/v0.14.0...v0.14.1) (2023-07-31)
+
+**Note:** Version bump only for package @sphereon/ssi-sdk.express-support
+
+
+
+
+
+# [0.14.0](https://github.com/Sphereon-Opensource/SSI-SDK/compare/v0.13.0...v0.14.0) (2023-07-30)
+
+
+### Features
+
+* Add express builder, cors configurer, passport authentication and casbin authorization support for APIs. ([cb04fe8](https://github.com/Sphereon-Opensource/SSI-SDK/commit/cb04fe8b84ce6f4c840afef43d628f23cb8e9e36))
+* Add global web resolution provider. Add json error handler ([f19d1d1](https://github.com/Sphereon-Opensource/SSI-SDK/commit/f19d1d135a9944a6c9e4c6040c58e7563c4442f2))
+* Allow objects for error response. Improve json handling in error responses ([4151c73](https://github.com/Sphereon-Opensource/SSI-SDK/commit/4151c73b4cdeb931c0deb8b8f34ed9c215efe5ba))
diff --git a/packages/headless-web3-provider/LICENSE b/packages/headless-web3-provider/LICENSE
new file mode 100644
index 000000000..8a21446d8
--- /dev/null
+++ b/packages/headless-web3-provider/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [2022] [Sphereon BV]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/packages/headless-web3-provider/README.md b/packages/headless-web3-provider/README.md
new file mode 100644
index 000000000..78c242e58
--- /dev/null
+++ b/packages/headless-web3-provider/README.md
@@ -0,0 +1,25 @@
+
+
+
+
+
Express Support
+
+
+
+This plugin adds Express REST API support, used in multiple plugins. It allows to configure multiple express options, and provides defaults.
+It also add support for Passport based authentication, allowing integration with many different strategies/solutions, including OpenID Connect.
+Lastly it has an integration with Azure/Entra Active Directory
+
+Note: Be aware that this plugin only runs in NodeJS environments!
+
+## Installation
+
+```shell
+pnpm add @sphereon/ssi-sdk.express-support
+```
+
+## Build
+
+```shell
+pnpm build
+```
diff --git a/packages/headless-web3-provider/api-extractor.json b/packages/headless-web3-provider/api-extractor.json
new file mode 100644
index 000000000..94c2c6a9f
--- /dev/null
+++ b/packages/headless-web3-provider/api-extractor.json
@@ -0,0 +1,3 @@
+{
+ "extends": "../include/api-extractor-base.json"
+}
diff --git a/packages/headless-web3-provider/package.json b/packages/headless-web3-provider/package.json
new file mode 100644
index 000000000..e90dbf70c
--- /dev/null
+++ b/packages/headless-web3-provider/package.json
@@ -0,0 +1,59 @@
+{
+ "name": "@sphereon/ssi-sdk-web3.headless-provider",
+ "version": "0.14.1",
+ "source": "src/index.ts",
+ "main": "dist/index.js",
+ "types": "dist/index.d.ts",
+ "scripts": {
+ "build": "tsc --build",
+ "build:clean": "tsc --build --clean && tsc --build"
+ },
+ "dependencies": {
+ "@ethersproject/abstract-signer": "^5.7.0",
+ "@ethersproject/abstract-provider": "^5.7.0",
+ "@ethersproject/bytes": "^5.7.0",
+ "@ethersproject/random": "^5.7.0",
+ "@ethersproject/signing-key": "^5.7.0",
+ "@ethersproject/strings": "^5.7.0",
+ "@ethersproject/transactions": "^5.7.0",
+ "@ethersproject/wallet": "^5.7.0",
+ "@metamask/eth-sig-util": "^6.0.0",
+ "@veramo/core": "4.2.0",
+ "@veramo/key-manager": "4.2.0",
+ "uint8arrays": "3.1.1",
+ "casbin": "^5.26.1",
+ "cors": "^2.8.5",
+ "dotenv-flow": "^3.2.0",
+ "express": "^4.18.2",
+ "express-session": "^1.17.3",
+ "passport": "^0.6.0",
+ "rxjs": "^7.8.1"
+ },
+ "devDependencies": {
+ "@types/body-parser": "^1.19.2",
+ "@types/cors": "^2.8.13",
+ "@types/dotenv-flow": "^3.2.0",
+ "@types/express": "^4.17.17",
+ "@types/express-serve-static-core": "^4.17.35",
+ "@types/express-session": "^1.17.7",
+ "@types/passport": "^1.0.12",
+ "typescript": "4.9.5"
+ },
+ "files": [
+ "dist/**/*",
+ "src/**/*",
+ "README.md",
+ "LICENSE"
+ ],
+ "publishConfig": {
+ "access": "public"
+ },
+ "repository": "git@github.com:Sphereon-Opensource/SSI-SDK.git",
+ "author": "Sphereon ",
+ "license": "Apache-2.0",
+ "keywords": [
+ "Sphereon",
+ "SSI",
+ "Agent"
+ ]
+}
diff --git a/packages/headless-web3-provider/src/auth-utils.ts b/packages/headless-web3-provider/src/auth-utils.ts
new file mode 100644
index 000000000..326b06995
--- /dev/null
+++ b/packages/headless-web3-provider/src/auth-utils.ts
@@ -0,0 +1,85 @@
+import express, { NextFunction } from 'express'
+import passport from 'passport'
+import { EndpointArgs } from './types'
+
+export const checkUserIsInRole = (opts: { roles: string | string[] }) => (req: express.Request, res: express.Response, next: NextFunction) => {
+ if (!opts?.roles || opts.roles.length === 0) {
+ return next()
+ }
+ const roles = Array.isArray(opts.roles) ? opts.roles : [opts.roles]
+ if (!req?.user || !('role' in req.user)) {
+ return res.status(401).end()
+ }
+
+ // @ts-ignore
+ const hasRole = roles.find((role) => req.user.role.toLowerCase() === role.toLowerCase())
+ if (!hasRole) {
+ return res.status(403).end()
+ }
+
+ return next()
+}
+
+const checkAuthenticationImpl = (req: express.Request, res: express.Response, opts?: EndpointArgs) => {
+ if (!opts || !opts.authentication || opts.authentication.enabled === false) {
+ return
+ }
+ if (!opts.authentication.strategy) {
+ return res.status(401).end()
+ }
+ passport.authenticate(opts.authentication.strategy)
+
+ if (typeof req.isAuthenticated !== 'function' || !req.isAuthenticated()) {
+ return res.status(403).end()
+ }
+ return
+}
+const checkAuthorizationImpl = (req: express.Request, res: express.Response, opts?: EndpointArgs) => {
+ if (!opts || !opts.authentication || !opts.authorization || opts.authentication.enabled === false || opts?.authorization.enabled === false) {
+ return
+ }
+ const authorization = opts.authorization
+
+ if (!authorization.enforcer && (!authorization.requireUserInRoles || authorization.requireUserInRoles.length === 0)) {
+ console.log(`Authorization enabled for endpoint, but no enforcer or roles supplied`)
+ return res.status(401).end()
+ }
+ if (authorization.requireUserInRoles && authorization.requireUserInRoles.length > 0) {
+ checkUserIsInRole({ roles: authorization.requireUserInRoles })
+ }
+ if (authorization.enforcer) {
+ const enforcer = authorization.enforcer
+ const permitted = enforcer.enforceSync(req.user, opts.resource, opts.operation)
+ if (!permitted) {
+ console.log(`Access to ${opts.resource} and op ${opts.operation} not allowed for ${req.user}`)
+ return res.status(403).end()
+ }
+ }
+ return
+}
+
+const executeRequestHandlers = (req: express.Request, res: express.Response, next: NextFunction, opts?: EndpointArgs) => {
+ if (opts?.handlers) {
+ opts.handlers.forEach((requestHandler) => requestHandler(req, res, next))
+ }
+}
+
+export const checkAuthenticationOnly = (opts?: EndpointArgs) => (req: express.Request, res: express.Response, next: NextFunction) => {
+ executeRequestHandlers(req, res, next, opts)
+ return checkAuthenticationImpl(req, res, opts) ?? next()
+}
+
+export const checkAuthorizationOnly = (opts?: EndpointArgs) => (req: express.Request, res: express.Response, next: NextFunction) => {
+ executeRequestHandlers(req, res, next, opts)
+ return checkAuthorizationImpl(req, res, opts) ?? next()
+}
+export const checkAuth = (opts?: EndpointArgs) => (req: express.Request, res: express.Response, next: NextFunction) => {
+ /*const handlers = /!*this._handlers ??*!/ []
+ checkAuthenticationImpl(req, res, opts))
+ handlers.push(checkAuthorizationImpl(req, res, opts))
+ handlers.push(next)
+ return handlers
+*/
+ executeRequestHandlers(req, res, next, opts)
+ return checkAuthenticationImpl(req, res, opts) ?? checkAuthorizationImpl(req, res, opts) ?? next()
+}
diff --git a/packages/headless-web3-provider/src/builders.ts b/packages/headless-web3-provider/src/builders.ts
new file mode 100644
index 000000000..4e7fd295a
--- /dev/null
+++ b/packages/headless-web3-provider/src/builders.ts
@@ -0,0 +1,292 @@
+/**
+ * @public
+ */
+import bodyParser from 'body-parser'
+import { Enforcer } from 'casbin'
+import cors, { CorsOptions } from 'cors'
+import express, { Express } from 'express'
+import { Application, ApplicationRequestHandler } from 'express-serve-static-core'
+import session from 'express-session'
+import passport, { InitializeOptions } from 'passport'
+import { checkUserIsInRole } from './auth-utils'
+import { env, jsonErrorHandler } from './functions'
+import { ExpressBuildResult, IExpressServerOpts } from './types'
+import * as dotenv from 'dotenv-flow'
+
+dotenv.config()
+
+export class ExpressBuilder {
+ private existingExpress?: Express
+ private hostnameOrIP?: string
+ private port?: number
+ private _handlers?: ApplicationRequestHandler[] = []
+ private listenCallback?: () => void
+ private _startListen?: boolean | undefined = undefined
+ private readonly envVarPrefix?: string
+ private _corsConfigurer?: ExpressCorsConfigurer
+ private _sessionOpts?: session.SessionOptions
+ private _usePassportAuth?: boolean = false
+ private _passportInitOpts?: InitializeOptions
+ private _userIsInRole?: string | string[]
+ private _enforcer?: Enforcer
+
+ private constructor(opts?: { existingExpress?: Express; envVarPrefix?: string }) {
+ const { existingExpress, envVarPrefix } = opts ?? {}
+ if (existingExpress) {
+ this.withExpress(existingExpress)
+ }
+ this.envVarPrefix = envVarPrefix ?? ''
+ }
+
+ public static fromExistingExpress(opts?: { existingExpress?: Express; envVarPrefix?: string }) {
+ return new ExpressBuilder(opts ?? {})
+ }
+
+ public static fromServerOpts(opts: IExpressServerOpts & { envVarPrefix?: string }) {
+ const builder = new ExpressBuilder({ existingExpress: opts?.existingExpress, envVarPrefix: opts?.envVarPrefix })
+ return builder.withEnableListenOpts(opts)
+ }
+
+ public enableListen(startOnBuild?: boolean): this {
+ if (startOnBuild !== undefined) {
+ this._startListen = startOnBuild
+ }
+ return this
+ }
+
+ public withEnableListenOpts({
+ port,
+ hostnameOrIP,
+ callback,
+ startOnBuild,
+ }: {
+ port?: number
+ hostnameOrIP?: string
+ startOnBuild?: boolean
+ callback?: () => void
+ }): this {
+ port && this.withPort(port)
+ hostnameOrIP && this.withHostname(hostnameOrIP)
+ if (typeof callback === 'function') {
+ this.withListenCallback(callback)
+ }
+ this._startListen = startOnBuild !== false
+ return this
+ }
+
+ public withPort(port: number): this {
+ this.port = port
+ return this
+ }
+
+ public withHostname(hostnameOrIP: string): this {
+ this.hostnameOrIP = hostnameOrIP
+ return this
+ }
+
+ public withListenCallback(callback: () => void): this {
+ this.listenCallback = callback
+ return this
+ }
+
+ public withExpress(existingExpress: Express): this {
+ this.existingExpress = existingExpress
+ this._startListen = false
+ return this
+ }
+
+ public withCorsConfigurer(configurer: ExpressCorsConfigurer): this {
+ this._corsConfigurer = configurer
+ return this
+ }
+
+ public withPassportAuth(usePassport: boolean, initializeOptions?: InitializeOptions): this {
+ this._usePassportAuth = usePassport
+ this._passportInitOpts = initializeOptions
+ return this
+ }
+
+ public withGlobalUserIsInRole(userIsInRole: string | string[]): this {
+ this._userIsInRole = userIsInRole
+ return this
+ }
+
+ public withEnforcer(enforcer: Enforcer): this {
+ this._enforcer = enforcer
+ return this
+ }
+
+ public startListening(express: Express) {
+ return express.listen(this.getPort(), this.getHostname(), this.listenCallback)
+ }
+
+ public getHostname(): string {
+ return this.hostnameOrIP ?? env('HOSTNAME', this.envVarPrefix) ?? '0.0.0.0'
+ }
+
+ public getPort(): number {
+ return (this.port ?? env('PORT', this.envVarPrefix) ?? 5000) as number
+ }
+
+ public setHandlers(handlers: ApplicationRequestHandler | ApplicationRequestHandler[]): this {
+ if (Array.isArray(handlers)) {
+ this._handlers = handlers
+ } else if (handlers) {
+ if (!this._handlers) {
+ this._handlers = []
+ }
+ this._handlers.push(handlers)
+ } else {
+ this._handlers = []
+ }
+
+ return this
+ }
+
+ public addHandler(handler: ApplicationRequestHandler): this {
+ if (!this._handlers) {
+ this._handlers = []
+ }
+ this._handlers.push(handler)
+ return this
+ }
+
+ public withSessionOptions(sessionOpts: session.SessionOptions): this {
+ this._sessionOpts = sessionOpts
+ return this
+ }
+
+ public build(opts?: {
+ express?: Express
+ startListening?: boolean
+ handlers?: ApplicationRequestHandler | ApplicationRequestHandler[]
+ }): ExpressBuildResult {
+ const express = this.buildExpress(opts)
+ return {
+ express,
+ port: this.getPort(),
+ hostname: this.getHostname(),
+ userIsInRole: this._userIsInRole,
+ startListening: this._startListen !== false,
+ enforcer: this._enforcer,
+ }
+ }
+
+ protected buildExpress(opts?: {
+ express?: Express
+ startListening?: boolean
+ handlers?: ApplicationRequestHandler | ApplicationRequestHandler[]
+ }): express.Express {
+ const app: express.Express = opts?.express ?? this.existingExpress ?? express()
+ if (this._sessionOpts) {
+ // @ts-ignore
+ app.use(session(this._sessionOpts))
+ }
+ if (this._usePassportAuth) {
+ app.use(passport.initialize(this._passportInitOpts))
+ if (this._sessionOpts) {
+ app.use(passport.session())
+ }
+ }
+ if (this._userIsInRole) {
+ app.use(checkUserIsInRole({ roles: this._userIsInRole }))
+ }
+ if (this._corsConfigurer) {
+ this._corsConfigurer.configure({ existingExpress: app })
+ }
+
+ app.use(jsonErrorHandler)
+
+ // @ts-ignore
+ this._handlers && this._handlers.length > 0 && app.use(this._handlers)
+ // @ts-ignore
+ opts?.handlers && app.use(opts.handlers)
+
+ app.use(bodyParser.urlencoded({ extended: true }))
+ app.use(bodyParser.json())
+
+ if (this._startListen !== false) {
+ this.startListening(app)
+ }
+ return app
+ }
+}
+
+export class ExpressCorsConfigurer {
+ private _disableCors?: boolean
+ private _enablePreflightOptions?: boolean
+ private _allowOrigin?: boolean | string | RegExp | Array
+ private _allowMethods?: string | string[]
+ private _allowedHeaders?: string | string[]
+ private _allowCredentials?: boolean
+ private readonly _express?: Express
+ private readonly _envVarPrefix?: string
+
+ constructor({ existingExpress, envVarPrefix }: { existingExpress?: Express; envVarPrefix?: string }) {
+ this._express = existingExpress
+ this._envVarPrefix = envVarPrefix
+ }
+
+ public allowOrigin(value: string | boolean | RegExp | Array): this {
+ this._allowOrigin = value
+ return this
+ }
+
+ public disableCors(value: boolean): this {
+ this._disableCors = value
+ return this
+ }
+
+ public allowMethods(value: string | string[]): this {
+ this._allowMethods = value
+ return this
+ }
+
+ public allowedHeaders(value: string | string[]): this {
+ this._allowedHeaders = value
+ return this
+ }
+
+ public allowCredentials(value: boolean): this {
+ this._allowCredentials = value
+ return this
+ }
+
+ public configure({ existingExpress }: { existingExpress?: Express }) {
+ const express = existingExpress ?? this._express
+ if (!express) {
+ throw Error('No express passed in during construction or configure')
+ }
+
+ const disableCorsEnv = env('CORS_DISABLE', this._envVarPrefix)
+ const corsDisabled = this._disableCors ?? (disableCorsEnv ? /true/.test(disableCorsEnv) : false)
+ if (corsDisabled) {
+ return
+ }
+ const envAllowOriginStr = env('CORS_ALLOW_ORIGIN', this._envVarPrefix) ?? '*'
+ let envAllowOrigin: string[] | string
+ if (envAllowOriginStr.includes(',')) {
+ envAllowOrigin = envAllowOriginStr.split(',')
+ } else if (envAllowOriginStr.includes(' ')) {
+ envAllowOrigin = envAllowOriginStr.split(' ')
+ } else {
+ envAllowOrigin = envAllowOriginStr
+ }
+ if (Array.isArray(envAllowOrigin) && envAllowOrigin.length === 1) {
+ envAllowOrigin = envAllowOrigin[0]
+ }
+ const corsOptions: CorsOptions = {
+ origin: this._allowOrigin ?? envAllowOrigin,
+ // todo: env vars
+ ...(this._allowMethods && { methods: this._allowMethods }),
+ ...(this._allowedHeaders && { allowedHeaders: this._allowedHeaders }),
+ ...(this._allowCredentials !== undefined && { credentials: this._allowCredentials }),
+ optionsSuccessStatus: 204,
+ }
+
+ if (this._enablePreflightOptions) {
+ express.options('*', cors(corsOptions))
+ }
+ express.use(cors(corsOptions))
+ }
+}
diff --git a/packages/headless-web3-provider/src/event-emitter.ts b/packages/headless-web3-provider/src/event-emitter.ts
new file mode 100644
index 000000000..88420d202
--- /dev/null
+++ b/packages/headless-web3-provider/src/event-emitter.ts
@@ -0,0 +1,39 @@
+export class EventEmitter {
+ private readonly listeners: Record void>> =
+ Object.create(null)
+
+ emit(eventName: string, ...args: any[]): boolean {
+ this.listeners[eventName]?.forEach((listener) => {
+ listener(...args)
+ })
+ return true
+ }
+
+ on(eventName: string, listener: (...args: any[]) => void): this {
+ this.listeners[eventName] ??= []
+ this.listeners[eventName]?.push(listener)
+ return this
+ }
+
+ off(eventName: string, listener: (...args: any[]) => void): this {
+ const listeners = this.listeners[eventName] ?? []
+
+ for (const [i, listener_] of listeners.entries()) {
+ if (listener === listener_) {
+ listeners.splice(i, 1)
+ break
+ }
+ }
+
+ return this
+ }
+
+ once(eventName: string, listener: (...args: any[]) => void): this {
+ const cb = (...args: any[]): void => {
+ this.off(eventName, cb)
+ listener(...args)
+ }
+
+ return this.on(eventName, cb)
+ }
+}
diff --git a/packages/headless-web3-provider/src/functions.ts b/packages/headless-web3-provider/src/functions.ts
new file mode 100644
index 000000000..5eb95bce0
--- /dev/null
+++ b/packages/headless-web3-provider/src/functions.ts
@@ -0,0 +1,31 @@
+import express, { NextFunction } from 'express'
+import process from 'process'
+
+export function env(key?: string, prefix?: string): string | undefined {
+ if (!key) {
+ return
+ }
+ return process.env[`${prefix ? prefix.trim() : ''}${key}`]
+}
+
+export function sendErrorResponse(response: express.Response, statusCode: number, message: string | object, error?: Error) {
+ console.log(message)
+ if (error) {
+ console.log(error)
+ }
+ response.statusCode = statusCode
+ if (typeof message === 'string' && !message.startsWith('{')) {
+ message = { error: message }
+ }
+ if (typeof message === 'string' && message.startsWith('{')) {
+ return response.status(statusCode).end(message)
+ }
+ return response.status(statusCode).json(message)
+}
+
+export const jsonErrorHandler = (err: any, req: express.Request, res: express.Response, next: NextFunction) => {
+ if (res.headersSent) {
+ return next(err)
+ }
+ return sendErrorResponse(res, 500, err.message, err)
+}
diff --git a/packages/headless-web3-provider/src/headless-web3-provider.ts b/packages/headless-web3-provider/src/headless-web3-provider.ts
new file mode 100644
index 000000000..31eb2e499
--- /dev/null
+++ b/packages/headless-web3-provider/src/headless-web3-provider.ts
@@ -0,0 +1,358 @@
+import {Signer} from "@ethersproject/abstract-signer";
+import {
+ filter,
+ firstValueFrom,
+ BehaviorSubject,
+ switchMap,
+ from,
+ first,
+ tap,
+} from 'rxjs'
+import {signTypedData, SignTypedDataVersion} from '@metamask/eth-sig-util'
+import assert from 'assert/strict'
+
+import {Web3RequestKind} from './utils'
+import {
+ ChainDisconnected,
+ Deny,
+ Disconnected,
+ ErrorWithCode,
+ Unauthorized,
+ UnsupportedMethod,
+} from './errors'
+import {ChainConnection, IWeb3Provider, PendingRequest, Web3ProviderConfig} from './types'
+import {EventEmitter} from './event-emitter'
+
+
+export class KMSWeb3Provider extends EventEmitter implements IWeb3Provider {
+ private _pendingRequests = new BehaviorSubject([])
+ private _signers: Signer[] = []
+ private _activeChainId: number
+ private _rpc: Record = {}
+ private _config: { debug: boolean; logger: typeof console.log }
+ private _authorizedRequests: { [K in Web3RequestKind | string]?: boolean } = {}
+
+ constructor(privateKeys: string[], private readonly chains: ChainConnection[], config: Web3ProviderConfig = {}) {
+ super()
+ this._signers = privateKeys.map((key) => new ethers.Wallet(key))
+ this._activeChainId = chains[0].chainId
+ this._config = Object.assign({debug: true, logger: console.log}, config)
+ }
+
+ request(args: { method: 'eth_accounts'; params: [] }): Promise
+ request(args: {
+ method: 'eth_requestAccounts'
+ params: string[]
+ }): Promise
+ request(args: { method: 'net_version'; params: [] }): Promise
+ request(args: { method: 'eth_chainId'; params: [] }): Promise
+ request(args: { method: 'personal_sign'; params: string[] }): Promise
+ request(args: {
+ method: 'eth_signTypedData' | 'eth_signTypedData_v1'
+ params: [object[], string]
+ }): Promise
+ request(args: {
+ method: 'eth_signTypedData_v3' | 'eth_signTypedData_v4'
+ params: string[]
+ }): Promise
+ async request({
+ method,
+ params,
+ }: {
+ method: string
+ params: any[]
+ }): Promise {
+ if (this._config.debug) {
+ this._config.logger({method, params})
+ }
+
+ switch (method) {
+ case 'eth_call':
+ case 'eth_estimateGas':
+ case 'eth_blockNumber':
+ case 'eth_getBlockByNumber':
+ case 'eth_getTransactionByHash':
+ case 'eth_getTransactionReceipt':
+ return this.getRpc().send(method, params)
+
+ case 'eth_requestAccounts':
+ case 'eth_accounts':
+ return this.waitAuthorization(
+ {method, params},
+ async () => {
+ const {chainId} = this.getCurrentChain()
+ this.emit('connect', {chainId})
+ return Promise.all(
+ this._signers.map((wallet) => wallet.getAddress())
+ )
+ },
+ true,
+ 'eth_requestAccounts'
+ )
+
+ case 'eth_chainId': {
+ const {chainId} = this.getCurrentChain()
+ return '0x' + chainId.toString(16)
+ }
+
+ case 'net_version': {
+ const {chainId} = this.getCurrentChain()
+ return chainId
+ }
+
+ case 'eth_sendTransaction': {
+ return this.waitAuthorization({method, params}, async () => {
+ const wallet = this.getCurrentWallet()
+ const rpc = this.getRpc()
+ const {gas, ...txRequest} = params[0]
+ const tx = await wallet.connect(rpc).sendTransaction(txRequest)
+ return tx.hash
+ })
+ }
+
+ case 'wallet_addEthereumChain': {
+ return this.waitAuthorization({method, params}, async () => {
+ const chainId = Number(params[0].chainId)
+ const rpcUrl = params[0].rpcUrl
+ this.addNetwork(chainId, rpcUrl)
+ return null
+ })
+ }
+
+ case 'wallet_switchEthereumChain': {
+ if (this._activeChainId === Number(params[0].chainId)) {
+ return null
+ }
+ return this.waitAuthorization({method, params}, async () => {
+ const chainId = Number(params[0].chainId)
+ this.switchNetwork(chainId)
+ return null
+ })
+ }
+
+ case 'personal_sign': {
+ return this.waitAuthorization({method, params}, async () => {
+ const wallet = this.getCurrentWallet()
+ const address = await wallet.getAddress()
+ assert.equal(address, ethers.getAddress(params[1]))
+ const message = toUtf8String(params[0])
+
+ const signature = await wallet.signMessage(message)
+ if (this._config.debug) {
+ this._config.logger('personal_sign', {
+ message,
+ signature,
+ })
+ }
+
+ return signature
+ })
+ }
+
+ case 'eth_signTypedData':
+ case 'eth_signTypedData_v1': {
+ return this.waitAuthorization({method, params}, async () => {
+ const wallet = this.getCurrentWallet() as Wallet
+ const address = await wallet.getAddress()
+ assert.equal(address, ethers.getAddress(params[1]))
+
+ const msgParams = params[0]
+
+ return signTypedData({
+ privateKey: Buffer.from(wallet.privateKey.slice(2), 'hex'),
+ data: msgParams,
+ version: SignTypedDataVersion.V1,
+ })
+ })
+ }
+
+ case 'eth_signTypedData_v3':
+ case 'eth_signTypedData_v4': {
+ return this.waitAuthorization({method, params}, async () => {
+ const wallet = this.getCurrentWallet() as Wallet
+ const address = await wallet.getAddress()
+ assert.equal(address, ethers.getAddress(params[0]))
+
+ const msgParams = JSON.parse(params[1])
+
+ return signTypedData({
+ privateKey: Buffer.from(wallet.privateKey.slice(2), 'hex'),
+ data: msgParams,
+ version:
+ method === 'eth_signTypedData_v4'
+ ? SignTypedDataVersion.V4
+ : SignTypedDataVersion.V3,
+ })
+ })
+ }
+
+ default:
+ throw UnsupportedMethod()
+ }
+ }
+
+ getCurrentWallet(): Signer {
+ const wallet = this._signers[0]
+
+ if (!wallet) {
+ throw Unauthorized()
+ }
+
+ return wallet
+ }
+
+ waitAuthorization(
+ requestInfo: PendingRequest['requestInfo'],
+ task: () => Promise,
+ permanentPermission = false,
+ methodOverride?: string
+ ) {
+ const method = methodOverride ?? requestInfo.method
+
+ if (this._authorizedRequests[method]) {
+ return task()
+ }
+
+ return new Promise((resolve, reject) => {
+ const pendingRequest: PendingRequest = {
+ requestInfo: requestInfo,
+ authorize: async () => {
+ if (permanentPermission) {
+ this._authorizedRequests[method] = true
+ }
+
+ resolve(await task())
+ },
+ reject(err) {
+ reject(err)
+ },
+ }
+
+ this._pendingRequests.next(
+ this._pendingRequests.getValue().concat(pendingRequest)
+ )
+ })
+ }
+
+ private consumeRequest(requestKind: Web3RequestKind) {
+ return firstValueFrom(
+ this._pendingRequests.pipe(
+ switchMap((a) => from(a)),
+ filter((request) => {
+ return request.requestInfo.method === requestKind
+ }),
+ first(),
+ tap((item) => {
+ this._pendingRequests.next(
+ without(this._pendingRequests.getValue(), item)
+ )
+ })
+ )
+ )
+ }
+
+ private consumeAllRequests() {
+ const pendingRequests = this._pendingRequests.getValue()
+ this._pendingRequests.next([])
+ return pendingRequests
+ }
+
+ getPendingRequests(): PendingRequest['requestInfo'][] {
+ return this._pendingRequests.getValue().map((pendingRequest) => pendingRequest.requestInfo)
+ }
+
+ getPendingRequestCount(requestKind?: Web3RequestKind): number {
+ const pendingRequests = this._pendingRequests.getValue()
+ if (!requestKind) {
+ return pendingRequests.length
+ }
+
+ return pendingRequests.filter(
+ (pendingRequest) => pendingRequest.requestInfo.method === requestKind
+ ).length
+ }
+
+ async authorize(requestKind: Web3RequestKind): Promise {
+ const pendingRequest = await this.consumeRequest(requestKind)
+ return pendingRequest.authorize()
+ }
+
+ async reject(
+ requestKind: Web3RequestKind,
+ reason: ErrorWithCode = Deny()
+ ): Promise {
+ const pendingRequest = await this.consumeRequest(requestKind)
+ return pendingRequest.reject(reason)
+ }
+
+ authorizeAll(): void {
+ this.consumeAllRequests().forEach((request) => request.authorize())
+ }
+
+ rejectAll(reason: ErrorWithCode = Deny()): void {
+ this.consumeAllRequests().forEach((request) => request.reject(reason))
+ }
+
+ async changeAccounts(privateKeys: string[]): Promise {
+ this._signers = privateKeys.map((key) => new ethers.Wallet(key))
+ this.emit(
+ 'accountsChanged',
+ await Promise.all(this._signers.map((wallet) => wallet.getAddress()))
+ )
+ }
+
+ private getCurrentChain(): ChainConnection {
+ const chainConn = this.chains.find(
+ chainConn => chainConn.chainId === this._activeChainId
+ )
+ if (!chainConn) {
+ throw Disconnected()
+ }
+ return chainConn
+ }
+
+ private getRpc(): ethers.JsonRpcProvider {
+ const chainConn = this.getCurrentChain()
+ let rpc = this._rpc[chainConn.chainId]
+
+ if (!rpc) {
+ rpc = new ethers.JsonRpcProvider(chainConn.rpcUrl, chainConn.chainId)
+ this._rpc[chainConn.chainId] = rpc
+ }
+
+ return rpc
+ }
+
+ getNetwork(): ChainConnection {
+ return this.getCurrentChain()
+ }
+
+ getNetworks(): ChainConnection[] {
+ return this.chains
+ }
+
+ addNetwork(chainId: number, rpcUrl: string): void {
+ this.chains.push({chainId, rpcUrl})
+ }
+
+ switchNetwork(chainId: number): void {
+ const idx = this.chains.findIndex(
+ connection => connection.chainId === chainId
+ )
+ if (idx < 0) {
+ throw ChainDisconnected()
+ }
+ if (chainId !== this._activeChainId) {
+ this._activeChainId = chainId
+ this.emit('chainChanged', chainId)
+ }
+ }
+}
+
+function without(list: T[], item: T): T[] {
+ const idx = list.indexOf(item)
+ if (idx >= 0) {
+ return list.slice(0, idx).concat(list.slice(idx + 1))
+ }
+ return list
+}
diff --git a/packages/headless-web3-provider/src/index.ts b/packages/headless-web3-provider/src/index.ts
new file mode 100644
index 000000000..557baa604
--- /dev/null
+++ b/packages/headless-web3-provider/src/index.ts
@@ -0,0 +1,4 @@
+export * from './auth-utils'
+export * from './builders'
+export * from './types'
+export { sendErrorResponse, jsonErrorHandler } from './functions'
diff --git a/packages/headless-web3-provider/src/signer.ts b/packages/headless-web3-provider/src/signer.ts
new file mode 100644
index 000000000..0d86e6e38
--- /dev/null
+++ b/packages/headless-web3-provider/src/signer.ts
@@ -0,0 +1,111 @@
+import {Provider, TransactionRequest} from "@ethersproject/abstract-provider";
+import {Signer, TypedDataDomain, TypedDataField, TypedDataSigner} from "@ethersproject/abstract-signer";
+import {arrayify} from "@ethersproject/bytes";
+import { serialize } from "@ethersproject/transactions";
+import {IKey, ManagedKeyInfo} from "@veramo/core";
+import {AbstractKeyManagementSystem, Eip712Payload} from "@veramo/key-manager";
+import * as u8a from 'uint8arrays'
+
+
+export class Web3KMSSignerBuilder {
+ private kms?: AbstractKeyManagementSystem
+ private keyRef?: Pick
+ private provider?: Provider
+
+ withKms(kms: AbstractKeyManagementSystem): this {
+ this.kms = kms
+ return this
+ }
+
+ withKid(kid: string): this {
+ this.keyRef = {kid}
+ return this
+ }
+
+ withKeyRef(keyRef: Pick | string): this {
+ if (typeof keyRef === 'string') {
+ return this.withKid(keyRef)
+ }
+
+ this.keyRef = keyRef
+ return this
+ }
+
+ withProvider(provider: Provider): this {
+ this.provider = provider
+ return this
+ }
+
+ build() {
+ if (!this.kms) {
+ throw Error('KMS needs to be provided')
+ }
+ if (!this.keyRef) {
+ throw Error('Keyref needs to be provided')
+ }
+ return new Web3KMSSigner({kms: this.kms, keyRef: this.keyRef, provider: this.provider})
+ }
+}
+
+/**
+ * This is a Web3 signer that delegates back to the KMS for the actual signatures.
+ * This means we do not expose private keys and can use any Secp256k1 key stored in the KMS if we want
+ *
+ * Be aware that the provided KeyRef needs to belong to the respective KMS, as it will use a lookup for the key in the KMS to sign
+ */
+export class Web3KMSSigner extends Signer implements TypedDataSigner {
+ private readonly kms: AbstractKeyManagementSystem
+ private readonly keyRef: Pick
+
+
+ constructor({provider, kms, keyRef}: {
+ provider?: Provider,
+ kms: AbstractKeyManagementSystem,
+ keyRef: Pick
+ }) {
+ super(provider);
+ this.kms = kms;
+ this.keyRef = keyRef
+ }
+
+
+
+ private async getKey(): Promise {
+ const keys = await this.kms.listKeys();
+ return keys.find(key => key.kid === this.keyRef.kid);
+ }
+
+ async signTransaction(tx: TransactionRequest): Promise {
+ return this.kms.sign({
+ algorithm: 'eth_signTransaction',
+ keyRef: this.keyRef,
+ // @ts-ignore
+ data: arrayify(serialize(tx))
+ })
+ }
+
+ async signMessage(message: string | Uint8Array): Promise {
+ return this.kms.sign({
+ algorithm: 'eth_signMessage',
+ keyRef: this.keyRef,
+ data: typeof message === 'string' ? u8a.fromString(message) : message
+ })
+ }
+
+ async _signTypedData(domain: TypedDataDomain, types: Record>, value: Record): Promise {
+ const jsonData: Partial = {
+ domain,
+ types,
+ message: value
+ }
+ return this.kms.sign({
+ algorithm: 'eth_signTypedData',
+ keyRef: this.keyRef,
+ data: u8a.fromString(JSON.stringify(jsonData))
+ })
+ }
+
+ async getAddress(): Promise {
+ return `0x${await this.getKey().then(key => key?.publicKeyHex)}`
+ }
+}
diff --git a/packages/headless-web3-provider/src/types.ts b/packages/headless-web3-provider/src/types.ts
new file mode 100644
index 000000000..1eec6aa22
--- /dev/null
+++ b/packages/headless-web3-provider/src/types.ts
@@ -0,0 +1,91 @@
+import { Enforcer } from 'casbin'
+import { Express, RequestHandler } from 'express'
+import { Strategy } from 'passport'
+
+export interface IWeb3Provider {
+ isMetaMask?: boolean
+
+ request(args: { method: 'eth_accounts'; params: [] }): Promise
+ request(args: {
+ method: 'eth_requestAccounts'
+ params: []
+ }): Promise
+ request(args: { method: 'net_version'; params: [] }): Promise
+ request(args: { method: 'eth_chainId'; params: [] }): Promise
+ request(args: { method: 'personal_sign'; params: string[] }): Promise
+ request(args: {
+ method: 'eth_signTypedData' | 'eth_signTypedData_v1'
+ params: [object[], string]
+ }): Promise
+ request(args: {
+ method: 'eth_signTypedData_v3' | 'eth_signTypedData_v4'
+ params: string[]
+ }): Promise
+ request(args: { method: string; params?: any[] }): Promise
+
+ emit(eventName: string, ...args: any[]): void
+ on(eventName: string, listener: (eventName: string) => void): void
+}
+
+export interface PendingRequest {
+ requestInfo: { method: string; params: any[] }
+ reject: (err: { message?: string; code?: number }) => void
+ authorize: () => Promise
+}
+
+export interface ChainConnection {
+ chainId: number
+ rpcUrl: string
+}
+
+export interface Web3ProviderConfig {
+ debug?: boolean
+ logger?: typeof console.log
+}
+
+
+
+export interface IExpressServerOpts {
+ port?: number // The port to listen on
+ cookieSigningKey?: string
+ hostname?: string // defaults to "0.0.0.0", meaning it will listen on all IP addresses. Can be an IP address or hostname
+ basePath?: string
+ existingExpress?: Express
+ listenCallback?: () => void
+ startListening?: boolean
+ // passport?: passport.PassportStatic
+ // externalBaseUrl?: string // In case an external base URL needs to be exposed
+}
+
+export interface ExpressBuildResult {
+ express: Express
+ port: number
+ hostname: string
+ userIsInRole?: string | string[]
+ startListening: boolean
+ enforcer?: Enforcer
+}
+
+export interface ISingleEndpointOpts {
+ endpoint?: EndpointArgs
+ enabled?: boolean
+ path?: string
+}
+
+export interface GenericAuthArgs {
+ authentication?: {
+ enabled?: boolean
+ strategy?: string | string[] | Strategy
+ }
+ authorization?: {
+ enabled?: boolean
+ requireUserInRoles?: string | string[]
+ enforcer?: Enforcer
+ }
+}
+
+export interface EndpointArgs extends GenericAuthArgs {
+ resource?: string
+ operation?: string
+ handlers?: RequestHandler[]
+}
diff --git a/packages/headless-web3-provider/tsconfig.json b/packages/headless-web3-provider/tsconfig.json
new file mode 100644
index 000000000..730108d34
--- /dev/null
+++ b/packages/headless-web3-provider/tsconfig.json
@@ -0,0 +1,9 @@
+{
+ "extends": "../tsconfig-base.json",
+ "compilerOptions": {
+ "rootDir": "src",
+ "outDir": "dist",
+ "declarationDir": "dist",
+ "strictPropertyInitialization": false
+ }
+}
diff --git a/packages/tsconfig.json b/packages/tsconfig.json
index 5e261c6d7..1e04d7185 100644
--- a/packages/tsconfig.json
+++ b/packages/tsconfig.json
@@ -27,6 +27,7 @@
{ "path": "wellknown-did-issuer" },
{ "path": "wellknown-did-verifier" },
{ "path": "kv-store" },
- { "path": "issuance-branding" }
+ { "path": "issuance-branding" },
+ { "path": "headless-web3-provider" }
]
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 59852758e..168c4ad52 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -332,6 +332,91 @@ importers:
specifier: ^1.0.12
version: 1.0.12
+ packages/headless-web3-provider:
+ dependencies:
+ '@ethersproject/abstract-provider':
+ specifier: ^5.7.0
+ version: 5.7.0
+ '@ethersproject/abstract-signer':
+ specifier: ^5.7.0
+ version: 5.7.0
+ '@ethersproject/bytes':
+ specifier: ^5.7.0
+ version: 5.7.0
+ '@ethersproject/random':
+ specifier: ^5.7.0
+ version: 5.7.0
+ '@ethersproject/signing-key':
+ specifier: ^5.7.0
+ version: 5.7.0
+ '@ethersproject/strings':
+ specifier: ^5.7.0
+ version: 5.7.0
+ '@ethersproject/transactions':
+ specifier: ^5.7.0
+ version: 5.7.0
+ '@ethersproject/wallet':
+ specifier: ^5.7.0
+ version: 5.7.0
+ '@metamask/eth-sig-util':
+ specifier: ^6.0.0
+ version: 6.0.0
+ '@veramo/core':
+ specifier: 4.2.0
+ version: 4.2.0(patch_hash=c5oempznsz4br5w3tcuk2i2mau)
+ '@veramo/key-manager':
+ specifier: 4.2.0
+ version: 4.2.0
+ casbin:
+ specifier: ^5.26.1
+ version: 5.26.1
+ cors:
+ specifier: ^2.8.5
+ version: 2.8.5
+ dotenv-flow:
+ specifier: ^3.2.0
+ version: 3.2.0
+ express:
+ specifier: ^4.18.2
+ version: 4.18.2
+ express-session:
+ specifier: ^1.17.3
+ version: 1.17.3
+ passport:
+ specifier: ^0.6.0
+ version: 0.6.0
+ rxjs:
+ specifier: ^7.8.1
+ version: 7.8.1
+ uint8arrays:
+ specifier: 3.1.1
+ version: 3.1.1
+ devDependencies:
+ '@types/body-parser':
+ specifier: ^1.19.2
+ version: 1.19.2
+ '@types/cors':
+ specifier: ^2.8.13
+ version: 2.8.13
+ '@types/dotenv-flow':
+ specifier: ^3.2.0
+ version: 3.2.0
+ '@types/express':
+ specifier: ^4.17.17
+ version: 4.17.17
+ '@types/express-serve-static-core':
+ specifier: ^4.17.35
+ version: 4.17.35
+ '@types/express-session':
+ specifier: ^1.17.7
+ version: 1.17.7
+ '@types/passport':
+ specifier: ^1.0.12
+ version: 1.0.12
+ typescript:
+ specifier: 4.9.5
+ version: 4.9.5
+
packages/issuance-branding:
dependencies:
'@sphereon/ssi-sdk.core':
@@ -3450,6 +3535,21 @@ packages:
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dev: true
+ /@ethereumjs/rlp@4.0.1:
+ resolution: {integrity: sha512-tqsQiBQDQdmPWE1xkkBq4rlSW5QZpLOUJ5RJh2/9fug+q9tnUhuZoVLk7s0scUIKTOzEtR72DFBXI4WiZcMpvw==}
+ engines: {node: '>=14'}
+ hasBin: true
+ dev: false
+
+ /@ethereumjs/util@8.1.0:
+ resolution: {integrity: sha512-zQ0IqbdX8FZ9aw11vP+dZkKDkS+kgIvQPHnSAXzP9pLu+Rfu3D3XEeLbicvoXJTYnhZiPmsZUxgdzXwNKxRPbA==}
+ engines: {node: '>=14'}
+ dependencies:
+ '@ethereumjs/rlp': 4.0.1
+ ethereum-cryptography: 2.1.2
+ micro-ftch: 0.3.1
+ dev: false
+
/@ethersproject/abi@5.7.0:
resolution: {integrity: sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA==}
dependencies:
@@ -4872,6 +4972,18 @@ packages:
- supports-color
optional: true
+ /@metamask/eth-sig-util@6.0.0:
+ resolution: {integrity: sha512-M0ezVz8lirXG1P6rHPzx+9i4zfhebCgVHE8XQT8VWxy/eUWllHQGcBcE8QmOusC7su55M4CMr9AyMIu0lx452g==}
+ engines: {node: '>=14.0.0'}
+ dependencies:
+ '@ethereumjs/util': 8.1.0
+ bn.js: 4.12.0
+ ethereum-cryptography: 2.1.2
+ ethjs-util: 0.1.6
+ tweetnacl: 1.0.3
+ tweetnacl-util: 0.15.1
+ dev: false
+
/@microsoft/api-extractor-model@7.25.3:
resolution: {integrity: sha512-WWxBUq77p2iZ+5VF7Nmrm3y/UtqCh5bYV8ii3khwq3w99+fXWpvfsAhgSLsC7k8XDQc6De4ssMxH6He/qe1pzg==}
dependencies:
@@ -4915,10 +5027,20 @@ packages:
resolution: {integrity: sha512-eMk0b9ReBbV23xXU693TAIrLyeO5iTgBZGSJfpqriG8UkYvr/hC9u9pyMlAakDNHWmbhMZCDs6KQO0jzKD8OTw==}
dev: true
+ /@noble/curves@1.1.0:
+ resolution: {integrity: sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA==}
+ dependencies:
+ '@noble/hashes': 1.2.0
+ dev: false
+
/@noble/ed25519@1.7.1:
resolution: {integrity: sha512-Rk4SkJFaXZiznFyC/t77Q0NKS4FL7TLJJsVG2V2oiEq3kJVeTdxysEe/yRWSpnWMe808XRDJ+VFh5pt/FN5plw==}
dev: true
+ /@noble/hashes@1.2.0:
+ resolution: {integrity: sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ==}
+ dev: false
+
/@noble/secp256k1@1.7.0:
resolution: {integrity: sha512-kbacwGSsH/CTout0ZnZWxnW1B+jH/7r/WAAKLBtrRJ/+CUH7lgmQzl3GTrQua3SGKWNSDsS6lmjnDpIJ5Dxyaw==}
dev: true
@@ -5767,6 +5889,21 @@ packages:
resolution: {integrity: sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==}
dev: false
+ /@scure/bip32@1.3.1:
+ resolution: {integrity: sha512-osvveYtyzdEVbt3OfwwXFr4P2iVBL5u1Q3q4ONBfDY/UpOuXmOlbgwc1xECEboY8wIays8Yt6onaWMUdUbfl0A==}
+ dependencies:
+ '@noble/curves': 1.1.0
+ '@noble/hashes': 1.2.0
+ '@scure/base': 1.1.1
+ dev: false
+
+ /@scure/bip39@1.2.1:
+ resolution: {integrity: sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==}
+ dependencies:
+ '@noble/hashes': 1.2.0
+ '@scure/base': 1.1.1
+ dev: false
+
/@segment/loosely-validate-event@2.0.0:
resolution: {integrity: sha512-ZMCSfztDBqwotkl848ODgVcAmN4OItEWDCkshcKz0/W6gGSQayuuCtWV/MlodFivAZD793d6UgANd6wCXUfrIw==}
dependencies:
@@ -10885,6 +11022,15 @@ packages:
meow: 5.0.0
dev: true
+ /ethereum-cryptography@2.1.2:
+ resolution: {integrity: sha512-Z5Ba0T0ImZ8fqXrJbpHcbpAvIswRte2wGNR/KePnu8GbbvgJ47lMxT/ZZPG6i9Jaht4azPDop4HaM00J0J59ug==}
+ dependencies:
+ '@noble/curves': 1.1.0
+ '@noble/hashes': 1.2.0
+ '@scure/bip32': 1.3.1
+ '@scure/bip39': 1.2.1
+ dev: false
+
/ethereum-public-key-to-address@0.0.2:
resolution: {integrity: sha512-KRd0yrlbgESK3A62L4sHiJRk+b/UPX92Ehd0cCXWa5L7bQaq7z5q4BSRhuUuSZj++LQHQfJQQnJkskuHjDnbCQ==}
hasBin: true
@@ -10895,6 +11041,14 @@ packages:
secp256k1: 3.8.0
dev: true
+ /ethjs-util@0.1.6:
+ resolution: {integrity: sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w==}
+ engines: {node: '>=6.5.0', npm: '>=3'}
+ dependencies:
+ is-hex-prefixed: 1.0.0
+ strip-hex-prefix: 1.0.0
+ dev: false
+
/ethr-did-resolver@8.1.2:
resolution: {integrity: sha512-dnbE3GItE1YHp/eavR11KbGDi8Il01H9GeH+wKgoSgE95pKBZufHyHYce/EK2k8VOmj6MJf8u/TIpPvxjCbK+A==}
dependencies:
@@ -12616,6 +12770,11 @@ packages:
dependencies:
is-extglob: 2.1.1
+ /is-hex-prefixed@1.0.0:
+ resolution: {integrity: sha512-WvtOiug1VFrE9v1Cydwm+FnXd3+w9GaeVUss5W4v/SLy3UW00vP+6iNF2SdnfiBoLy4bTqVdkftNGTUeOFVsbA==}
+ engines: {node: '>=6.5.0', npm: '>=3'}
+ dev: false
+
/is-interactive@1.0.0:
resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==}
engines: {node: '>=8'}
@@ -15171,6 +15330,10 @@ packages:
- supports-color
- utf-8-validate
+ /micro-ftch@0.3.1:
+ resolution: {integrity: sha512-/0LLxhzP0tfiR5hcQebtudP56gUurs2CLkGarnCiB/OqEyUFQ6U3paQi/tgLv0hBJYt2rnr9MNpxz4fiiugstg==}
+ dev: false
+
/micromatch@4.0.5:
resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==}
engines: {node: '>=8.6'}
@@ -18495,6 +18658,13 @@ packages:
resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==}
engines: {node: '>=6'}
+ /strip-hex-prefix@1.0.0:
+ resolution: {integrity: sha512-q8d4ue7JGEiVcypji1bALTos+0pWtyGlivAWyPuTkHzuTCJqrK9sWxYQZUq6Nq3cuyv3bm734IhHvHtGGURU6A==}
+ engines: {node: '>=6.5.0', npm: '>=3'}
+ dependencies:
+ is-hex-prefixed: 1.0.0
+ dev: false
+
/strip-indent@2.0.0:
resolution: {integrity: sha512-RsSNPLpq6YUL7QYy44RnPVTn/lcVZtb48Uof3X5JLbF4zD/Gs7ZFDv2HWol+leoQN2mT86LAzSshGfkTlSOpsA==}
engines: {node: '>=4'}
@@ -19087,9 +19257,12 @@ packages:
- supports-color
dev: true
+ /tweetnacl-util@0.15.1:
+ resolution: {integrity: sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw==}
+ dev: false
+
/tweetnacl@1.0.3:
resolution: {integrity: sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==}
- dev: true
/type-check@0.3.2:
resolution: {integrity: sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==}