OAuth1 to OAuth2

This commit is contained in:
simonredfern 2025-11-30 10:11:51 +01:00
parent a86f1455bb
commit d14fb57005
14 changed files with 583 additions and 401 deletions

499
package-lock.json generated
View File

@ -29,7 +29,6 @@
"langchain": "^0.3.19",
"markdown-it": "^14.1.0",
"node-fetch": "^2.6.7",
"oauth": "^0.10.0",
"obp-api-typescript": "^1.0.1",
"obp-typescript": "^1.0.36",
"pinia": "^2.0.37",
@ -80,6 +79,7 @@
"supertest": "^7.0.0",
"ts-jest": "^29.2.5",
"ts-node": "^10.9.1",
"tsx": "^4.20.6",
"typescript": "~5.2.2",
"unplugin-auto-import": "^0.18.0",
"unplugin-element-plus": "^0.8.0",
@ -1075,6 +1075,22 @@
"vue": "^3.2.0"
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
"integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
"cpu": [
"ppc64"
],
"dev": true,
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz",
@ -1331,6 +1347,22 @@
"node": ">=12"
}
},
"node_modules/@esbuild/netbsd-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
"integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz",
@ -1347,6 +1379,22 @@
"node": ">=12"
}
},
"node_modules/@esbuild/openbsd-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
"integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz",
@ -1363,6 +1411,22 @@
"node": ">=12"
}
},
"node_modules/@esbuild/openharmony-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
"integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"openharmony"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz",
@ -7268,6 +7332,18 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/get-tsconfig": {
"version": "4.13.0",
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz",
"integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==",
"dev": true,
"dependencies": {
"resolve-pkg-maps": "^1.0.0"
},
"funding": {
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
}
},
"node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
@ -11891,6 +11967,15 @@
"node": ">=4"
}
},
"node_modules/resolve-pkg-maps": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
"dev": true,
"funding": {
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
}
},
"node_modules/resolve.exports": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz",
@ -13187,6 +13272,418 @@
"node": ">=0.6.x"
}
},
"node_modules/tsx": {
"version": "4.20.6",
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.6.tgz",
"integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==",
"dev": true,
"dependencies": {
"esbuild": "~0.25.0",
"get-tsconfig": "^4.7.5"
},
"bin": {
"tsx": "dist/cli.mjs"
},
"engines": {
"node": ">=18.0.0"
},
"optionalDependencies": {
"fsevents": "~2.3.3"
}
},
"node_modules/tsx/node_modules/@esbuild/android-arm": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
"integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/android-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
"integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/android-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
"integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/darwin-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
"integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/darwin-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
"integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/freebsd-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
"integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/freebsd-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
"integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-arm": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
"integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
"integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-ia32": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
"integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-loong64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
"integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
"cpu": [
"loong64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-mips64el": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
"integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
"cpu": [
"mips64el"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-ppc64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
"integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
"cpu": [
"ppc64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-riscv64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
"integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
"cpu": [
"riscv64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-s390x": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
"integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
"cpu": [
"s390x"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
"integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/netbsd-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
"integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/openbsd-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
"integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/sunos-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
"integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/win32-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
"integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/win32-ia32": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
"integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/win32-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
"integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/esbuild": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
"integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
"dev": true,
"hasInstallScript": true,
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.25.12",
"@esbuild/android-arm": "0.25.12",
"@esbuild/android-arm64": "0.25.12",
"@esbuild/android-x64": "0.25.12",
"@esbuild/darwin-arm64": "0.25.12",
"@esbuild/darwin-x64": "0.25.12",
"@esbuild/freebsd-arm64": "0.25.12",
"@esbuild/freebsd-x64": "0.25.12",
"@esbuild/linux-arm": "0.25.12",
"@esbuild/linux-arm64": "0.25.12",
"@esbuild/linux-ia32": "0.25.12",
"@esbuild/linux-loong64": "0.25.12",
"@esbuild/linux-mips64el": "0.25.12",
"@esbuild/linux-ppc64": "0.25.12",
"@esbuild/linux-riscv64": "0.25.12",
"@esbuild/linux-s390x": "0.25.12",
"@esbuild/linux-x64": "0.25.12",
"@esbuild/netbsd-arm64": "0.25.12",
"@esbuild/netbsd-x64": "0.25.12",
"@esbuild/openbsd-arm64": "0.25.12",
"@esbuild/openbsd-x64": "0.25.12",
"@esbuild/openharmony-arm64": "0.25.12",
"@esbuild/sunos-x64": "0.25.12",
"@esbuild/win32-arm64": "0.25.12",
"@esbuild/win32-ia32": "0.25.12",
"@esbuild/win32-x64": "0.25.12"
}
},
"node_modules/tty-browserify": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz",

View File

@ -6,7 +6,7 @@
"jest"
],
"scripts": {
"dev": "vite & ts-node server/app.ts",
"dev": "vite & tsx --tsconfig tsconfig.server.json server/app.ts",
"build": "run-p build-only",
"build-server": "tsc --project tsconfig.server.json",
"preview": "vite preview",
@ -41,7 +41,6 @@
"langchain": "^0.3.19",
"markdown-it": "^14.1.0",
"node-fetch": "^2.6.7",
"oauth": "^0.10.0",
"obp-api-typescript": "^1.0.1",
"obp-typescript": "^1.0.36",
"pinia": "^2.0.37",
@ -92,6 +91,7 @@
"supertest": "^7.0.0",
"ts-jest": "^29.2.5",
"ts-node": "^10.9.1",
"tsx": "^4.20.6",
"typescript": "~5.2.2",
"unplugin-auto-import": "^0.18.0",
"unplugin-element-plus": "^0.8.0",

View File

@ -34,9 +34,14 @@ import express, { Application } from 'express'
import { useExpressServer, useContainer } from 'routing-controllers'
import { Container } from 'typedi'
import path from 'path'
import { fileURLToPath } from 'url'
import { execSync } from 'child_process'
import { OAuth2Service } from './services/OAuth2Service'
// Fix __dirname for ESM/tsx compatibility
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const port = 8085
const app: Application = express()
@ -109,41 +114,34 @@ useContainer(Container)
// Initialize OAuth2 Service
console.log(`--- OAuth2/OIDC setup -------------------------------------------`)
const useOAuth2 = process.env.VITE_USE_OAUTH2 === 'true'
console.log(`OAuth2/OIDC enabled: ${useOAuth2}`)
const wellKnownUrl = process.env.VITE_OBP_OAUTH2_WELL_KNOWN_URL
if (useOAuth2) {
const wellKnownUrl = process.env.VITE_OBP_OAUTH2_WELL_KNOWN_URL
if (!wellKnownUrl) {
console.warn('VITE_OBP_OAUTH2_WELL_KNOWN_URL not set. OAuth2 will not function.')
} else {
console.log(`OIDC Well-Known URL: ${wellKnownUrl}`)
// Get OAuth2Service from container
const oauth2Service = Container.get(OAuth2Service)
// Initialize OAuth2 service from OIDC discovery document
oauth2Service
.initializeFromWellKnown(wellKnownUrl)
.then(() => {
console.log('OAuth2Service: Initialization successful')
console.log(' Client ID:', process.env.VITE_OBP_OAUTH2_CLIENT_ID || 'NOT SET')
console.log(' Redirect URI:', process.env.VITE_OBP_OAUTH2_REDIRECT_URL || 'NOT SET')
console.log('OAuth2/OIDC ready for authentication')
})
.catch((error) => {
console.error('OAuth2Service: Initialization failed:', error.message)
console.error('OAuth2/OIDC authentication will not be available')
console.error('Please check:')
console.error(' 1. OBP-OIDC server is running')
console.error(' 2. VITE_OBP_OAUTH2_WELL_KNOWN_URL is correct')
console.error(' 3. Network connectivity to OIDC provider')
})
}
if (!wellKnownUrl) {
console.error('VITE_OBP_OAUTH2_WELL_KNOWN_URL not set. OAuth2 will not function.')
console.error('Please set this environment variable to continue.')
} else {
console.log('OAuth2/OIDC is disabled. Using OAuth 1.0a authentication.')
console.log('To enable OAuth2, set VITE_USE_OAUTH2=true in environment')
console.log(`OIDC Well-Known URL: ${wellKnownUrl}`)
// Get OAuth2Service from container
const oauth2Service = Container.get(OAuth2Service)
// Initialize OAuth2 service from OIDC discovery document
oauth2Service
.initializeFromWellKnown(wellKnownUrl)
.then(() => {
console.log('OAuth2Service: Initialization successful')
console.log(' Client ID:', process.env.VITE_OBP_OAUTH2_CLIENT_ID || 'NOT SET')
console.log(' Redirect URI:', process.env.VITE_OBP_OAUTH2_REDIRECT_URL || 'NOT SET')
console.log('OAuth2/OIDC ready for authentication')
})
.catch((error) => {
console.error('OAuth2Service: Initialization failed:', error.message)
console.error('OAuth2/OIDC authentication will not be available')
console.error('Please check:')
console.error(' 1. OBP-OIDC server is running')
console.error(' 2. VITE_OBP_OAUTH2_WELL_KNOWN_URL is correct')
console.error(' 3. Network connectivity to OIDC provider')
})
}
console.log(`-----------------------------------------------------------------`)

View File

@ -1,42 +0,0 @@
/*
* Open Bank Project - API Explorer II
* Copyright (C) 2023-2024, TESOBE GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Email: contact@tesobe.com
* TESOBE GmbH
* Osloerstrasse 16/17
* Berlin 13359, Germany
*
* This product includes software developed at
* TESOBE (http://www.tesobe.com/)
*
*/
import { Controller, Req, Res, Get, UseBefore } from 'routing-controllers'
import { Request, Response } from 'express'
import { Service } from 'typedi'
import OauthAccessTokenMiddleware from '../middlewares/OauthAccessTokenMiddleware'
@Service()
@Controller()
@UseBefore(OauthAccessTokenMiddleware)
// This controller seems to not do anything at all
export default class CallbackController {
@Get('/callback')
callback(@Req() request: Request, @Res() response: Response): Response {
return response
}
}

View File

@ -1,41 +0,0 @@
/*
* Open Bank Project - API Explorer II
* Copyright (C) 2023-2024, TESOBE GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Email: contact@tesobe.com
* TESOBE GmbH
* Osloerstrasse 16/17
* Berlin 13359, Germany
*
* This product includes software developed at
* TESOBE (http://www.tesobe.com/)
*
*/
import { Controller, Req, Res, Get, UseBefore } from 'routing-controllers'
import { Request, Response } from 'express'
import OauthRequestTokenMiddleware from '../middlewares/OauthRequestTokenMiddleware'
import { Service } from 'typedi'
@Service()
@Controller()
@UseBefore(OauthRequestTokenMiddleware)
export class ConnectController {
@Get('/connect')
connect(@Req() request: Request, @Res() response: Response): Response {
return response
}
}

View File

@ -28,7 +28,7 @@
import { Controller, Session, Req, Res, Get } from 'routing-controllers'
import { Request, Response } from 'express'
import OBPClientService from '../services/OBPClientService'
import OauthInjectedService from '../services/OauthInjectedService'
import { Service } from 'typedi'
import { OAuthConfig } from 'obp-typescript'
import { commitId } from '../app'
@ -43,10 +43,7 @@ export class StatusController {
'stored_procedure_vDec2019',
'rabbitmq_vOct2024'
]
constructor(
private obpClientService: OBPClientService,
private oauthInjectedService: OauthInjectedService
) {}
constructor(private obpClientService: OBPClientService) {}
@Get('/')
async index(
@Session() session: any,
@ -55,7 +52,10 @@ export class StatusController {
): Response {
const oauthConfig = session['clientConfig']
const version = this.obpClientService.getOBPVersion()
const currentUser = await this.obpClientService.get(`/obp/${version}/users/current`, oauthConfig)
const currentUser = await this.obpClientService.get(
`/obp/${version}/users/current`,
oauthConfig
)
const apiVersions = await this.checkApiVersions(oauthConfig, version)
const messageDocs = await this.checkMessagDocs(oauthConfig, version)
const resourceDocs = await this.checkResourceDocs(oauthConfig, version)
@ -85,10 +85,7 @@ export class StatusController {
async checkResourceDocs(oauthConfig: OAuthConfig, version: string): Promise<boolean> {
try {
const path = `/obp/${version}/resource-docs/${version}/obp`
const resourceDocs = await this.obpClientService.get(
path,
oauthConfig
)
const resourceDocs = await this.obpClientService.get(path, oauthConfig)
return !this.isCodeError(resourceDocs, path)
} catch (error) {
return false
@ -99,13 +96,7 @@ export class StatusController {
const messageDocsCodeResult = await Promise.all(
this.connectors.map(async (connector) => {
const path = `/obp/${version}/message-docs/${connector}`
return !this.isCodeError(
await this.obpClientService.get(
path,
oauthConfig
),
path
)
return !this.isCodeError(await this.obpClientService.get(path, oauthConfig), path)
})
)
return messageDocsCodeResult.every((isCodeError: boolean) => isCodeError)

View File

@ -28,20 +28,16 @@
import { Controller, Session, Req, Res, Get } from 'routing-controllers'
import { Request, Response } from 'express'
import OBPClientService from '../services/OBPClientService'
import OauthInjectedService from '../services/OauthInjectedService'
import { Service } from 'typedi'
import superagent from 'superagent'
import { OAuth2Service } from '../services/OAuth2Service'
@Service()
@Controller('/user')
export class UserController {
private obpExplorerHome = process.env.VITE_OBP_API_EXPLORER_HOST
private useOAuth2 = process.env.VITE_USE_OAUTH2 === 'true'
constructor(
private obpClientService: OBPClientService,
private oauthInjectedService: OauthInjectedService,
private oauth2Service: OAuth2Service
) {}
@Get('/logoff')
@ -52,11 +48,6 @@ export class UserController {
): Response {
console.log('UserController: Logging out user')
// Clear OAuth 1.0a session data
this.oauthInjectedService.requestTokenKey = undefined
this.oauthInjectedService.requestTokenSecret = undefined
session['clientConfig'] = undefined
// Clear OAuth2 session data
delete session['oauth2_access_token']
delete session['oauth2_refresh_token']
@ -95,10 +86,9 @@ export class UserController {
@Res() response: Response
): Response {
console.log('UserController: Getting current user')
console.log(' OAuth2 enabled:', this.useOAuth2)
// Check OAuth2 session first (if OAuth2 is enabled)
if (this.useOAuth2 && session['oauth2_user']) {
// Check OAuth2 session
if (session['oauth2_user']) {
console.log('UserController: Returning OAuth2 user info')
const oauth2User = session['oauth2_user']
@ -146,24 +136,8 @@ export class UserController {
})
}
// Fall back to OAuth 1.0a
console.log('UserController: Checking OAuth 1.0a session')
const oauthConfig = session['clientConfig']
if (!oauthConfig) {
console.log('UserController: No authentication session found')
return response.json({})
}
console.log('UserController: Returning OAuth 1.0a user info')
const version = this.obpClientService.getOBPVersion()
try {
const userData = await this.obpClientService.get(`/obp/${version}/users/current`, oauthConfig)
return response.json(userData)
} catch (error) {
console.error('UserController: Failed to get user from OBP API:', error)
return response.json({})
}
// No authentication session found
console.log('UserController: No authentication session found')
return response.json({})
}
}

View File

@ -27,7 +27,7 @@
import { ExpressMiddlewareInterface } from 'routing-controllers'
import { Request, Response } from 'express'
import { Service } from 'typedi'
import { Service, Container } from 'typedi'
import { OAuth2Service } from '../services/OAuth2Service'
import { PKCEUtils } from '../utils/pkce'
@ -58,7 +58,12 @@ import { PKCEUtils } from '../utils/pkce'
*/
@Service()
export default class OAuth2AuthorizationMiddleware implements ExpressMiddlewareInterface {
constructor(private oauth2Service: OAuth2Service) {}
private oauth2Service: OAuth2Service
constructor() {
// Explicitly get OAuth2Service from the container to avoid injection issues
this.oauth2Service = Container.get(OAuth2Service)
}
/**
* Handle the authorization request
@ -69,7 +74,14 @@ export default class OAuth2AuthorizationMiddleware implements ExpressMiddlewareI
async use(request: Request, response: Response): Promise<void> {
console.log('OAuth2AuthorizationMiddleware: Starting OAuth2 authorization flow')
// Check if OAuth2 service is initialized
// Check if OAuth2 service exists and is initialized
if (!this.oauth2Service) {
console.error('OAuth2AuthorizationMiddleware: OAuth2 service is null/undefined')
return response
.status(500)
.send('OAuth2 service not available. Please check server configuration.')
}
if (!this.oauth2Service.isInitialized()) {
console.error('OAuth2AuthorizationMiddleware: OAuth2 service not initialized')
return response
@ -140,9 +152,7 @@ export default class OAuth2AuthorizationMiddleware implements ExpressMiddlewareI
delete session['oauth2_flow_timestamp']
delete session['oauth2_redirect_page']
return response
.status(500)
.send(`Failed to initiate OAuth2 flow: ${error.message}`)
return response.status(500).send(`Failed to initiate OAuth2 flow: ${error.message}`)
}
}
}

View File

@ -27,7 +27,7 @@
import { ExpressMiddlewareInterface } from 'routing-controllers'
import { Request, Response } from 'express'
import { Service } from 'typedi'
import { Service, Container } from 'typedi'
import { OAuth2Service } from '../services/OAuth2Service'
import jwt from 'jsonwebtoken'
@ -60,7 +60,12 @@ import jwt from 'jsonwebtoken'
*/
@Service()
export default class OAuth2CallbackMiddleware implements ExpressMiddlewareInterface {
constructor(private oauth2Service: OAuth2Service) {}
private oauth2Service: OAuth2Service
constructor() {
// Explicitly get OAuth2Service from the container to avoid injection issues
this.oauth2Service = Container.get(OAuth2Service)
}
/**
* Handle the OAuth2 callback
@ -308,9 +313,7 @@ export default class OAuth2CallbackMiddleware implements ExpressMiddlewareInterf
// Get redirect page and clean up
const redirectPage =
(session['oauth2_redirect_page'] as string) ||
process.env.VITE_OBP_API_EXPLORER_HOST ||
'/'
(session['oauth2_redirect_page'] as string) || process.env.VITE_OBP_API_EXPLORER_HOST || '/'
delete session['oauth2_redirect_page']
console.log('OAuth2CallbackMiddleware: Redirecting to:', redirectPage)

View File

@ -1,96 +0,0 @@
/*
* Open Bank Project - API Explorer II
* Copyright (C) 2023-2024, TESOBE GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Email: contact@tesobe.com
* TESOBE GmbH
* Osloerstrasse 16/17
* Berlin 13359, Germany
*
* This product includes software developed at
* TESOBE (http://www.tesobe.com/)
*
*/
import { ExpressMiddlewareInterface } from 'routing-controllers'
import { Response, Request } from 'express'
import { Service } from 'typedi'
import OauthInjectedService from '../services/OauthInjectedService'
import OBPClientService from '../services/OBPClientService'
@Service()
export default class OauthAccessTokenMiddleware implements ExpressMiddlewareInterface {
constructor(
private obpClientService: OBPClientService,
private oauthInjectedService: OauthInjectedService
) {}
use(request: Request, response: Response): any {
const oauthService = this.oauthInjectedService
const consumer = oauthService.getConsumer()
const oauthVerifier = request.query.oauth_verifier
const session = request.session
console.log('OauthAccessTokenMiddleware.ts use says: Before consumer.getOAuthAccessToken')
consumer.getOAuthAccessToken(
oauthService.requestTokenKey,
oauthService.requestTokenSecret,
oauthVerifier,
(error: any, oauthTokenKey: string, oauthTokenSecret: string) => {
if (error) {
const errorStr = JSON.stringify(error)
console.error(errorStr)
response.status(500).send('Error getting OAuth access token: ' + errorStr)
} else {
const clientConfig = JSON.parse(
JSON.stringify(this.obpClientService.getOBPClientConfig())
) //Deep copy
clientConfig['oauthConfig']['accessToken'] = {
key: oauthTokenKey,
secret: oauthTokenSecret
}
console.log(`OauthAccessTokenMiddleware.ts use says: clientConfig: ${JSON.stringify(clientConfig)}`)
session['clientConfig'] = clientConfig
console.log('OauthAccessTokenMiddleware.ts use says: Seems OK, redirecting..')
let redirectPage: String
const obpExplorerHome = process.env.VITE_OBP_API_EXPLORER_HOST
if(!obpExplorerHome) {
console.error(`VITE_OBP_API_EXPLORER_HOST: ${obpExplorerHome}`)
}
if (session['redirectPage']) {
try {
redirectPage = session['redirectPage']
} catch (e) {
console.log('OauthAccessTokenMiddleware.ts use says: Error decoding redirect URI')
redirectPage = obpExplorerHome
}
} else {
redirectPage = obpExplorerHome
}
console.log(`OauthAccessTokenMiddleware.ts use says: Will redirect to: ${redirectPage}`)
console.log('OauthAccessTokenMiddleware.ts use says: Here comes the session:')
console.log(session)
response.redirect(redirectPage)
}
}
)
}
}

View File

@ -1,62 +0,0 @@
/*
* Open Bank Project - API Explorer II
* Copyright (C) 2023-2024, TESOBE GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Email: contact@tesobe.com
* TESOBE GmbH
* Osloerstrasse 16/17
* Berlin 13359, Germany
*
* This product includes software developed at
* TESOBE (http://www.tesobe.com/)
*
*/
import { ExpressMiddlewareInterface } from 'routing-controllers'
import { Response, Request } from 'express'
import { Service } from 'typedi'
import OauthInjectedService from '../services/OauthInjectedService'
@Service()
export default class OauthRequestTokenMiddleware implements ExpressMiddlewareInterface {
constructor(private oauthInjectedService: OauthInjectedService) {}
use(request: Request, response: Response): any {
const apiHost = process.env.VITE_OBP_API_PORTAL_HOST ? process.env.VITE_OBP_API_PORTAL_HOST : process.env.VITE_OBP_API_HOST
console.debug('process.env.VITE_OBP_API_PORTAL_HOST:', process.env.VITE_OBP_API_PORTAL_HOST)
const oauthService = this.oauthInjectedService
const consumer = oauthService.getConsumer()
const redirectPage = request.query.redirect
const session = request.session
if (redirectPage) {
session['redirectPage'] = redirectPage
}
consumer.getOAuthRequestToken((error: any, oauthTokenKey: string, oauthTokenSecret: string) => {
if (error) {
const errorStr = JSON.stringify(error)
console.error(errorStr)
response.status(500).send('Error getting OAuth request token: ' + errorStr)
} else {
oauthService.requestTokenKey = oauthTokenKey
oauthService.requestTokenSecret = oauthTokenSecret
console.log('OauthRequestTokenMiddleware.ts consumer.getOAuthRequestToken says: Redirecting to /oauth/authorize?oauth_token=XXX')
response.redirect(apiHost + '/oauth/authorize?oauth_token=' + oauthTokenKey)
}
})
}
}

View File

@ -237,12 +237,11 @@ export class OAuth2Service {
* authUrl.searchParams.set('code_challenge_method', 'S256')
* response.redirect(authUrl.toString())
*/
createAuthorizationURL(
state: string,
scopes: string[] = ['openid', 'profile', 'email']
): URL {
createAuthorizationURL(state: string, scopes: string[] = ['openid', 'profile', 'email']): URL {
if (!this.isInitialized() || !this.oidcConfig) {
throw new Error('OAuth2Service: Service not initialized. Call initializeFromWellKnown() first')
throw new Error(
'OAuth2Service: Service not initialized. Call initializeFromWellKnown() first'
)
}
console.log('OAuth2Service: Creating authorization URL')
@ -274,7 +273,9 @@ export class OAuth2Service {
*/
async exchangeCodeForTokens(code: string, codeVerifier: string): Promise<TokenResponse> {
if (!this.isInitialized() || !this.oidcConfig) {
throw new Error('OAuth2Service: Service not initialized. Call initializeFromWellKnown() first')
throw new Error(
'OAuth2Service: Service not initialized. Call initializeFromWellKnown() first'
)
}
console.log('OAuth2Service: Exchanging authorization code for tokens')
@ -316,7 +317,9 @@ export class OAuth2Service {
*/
async refreshAccessToken(refreshToken: string): Promise<TokenResponse> {
if (!this.isInitialized() || !this.oidcConfig) {
throw new Error('OAuth2Service: Service not initialized. Call initializeFromWellKnown() first')
throw new Error(
'OAuth2Service: Service not initialized. Call initializeFromWellKnown() first'
)
}
console.log('OAuth2Service: Refreshing access token')
@ -377,7 +380,9 @@ export class OAuth2Service {
*/
async getUserInfo(accessToken: string): Promise<UserInfo> {
if (!this.isInitialized() || !this.oidcConfig) {
throw new Error('OAuth2Service: Service not initialized. Call initializeFromWellKnown() first')
throw new Error(
'OAuth2Service: Service not initialized. Call initializeFromWellKnown() first'
)
}
console.log('OAuth2Service: Fetching user info')

View File

@ -1,55 +0,0 @@
/*
* Open Bank Project - API Explorer II
* Copyright (C) 2023-2024, TESOBE GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Email: contact@tesobe.com
* TESOBE GmbH
* Osloerstrasse 16/17
* Berlin 13359, Germany
*
* This product includes software developed at
* TESOBE (http://www.tesobe.com/)
*
*/
import { Service } from 'typedi'
import oauth from 'oauth'
@Service()
export default class OauthInjectedService {
public requestTokenKey: string
public requestTokenSecret: string
private oauth: oauth.OAuth
constructor() {
const apiHost = process.env.VITE_OBP_API_HOST
const consumerKey = process.env.VITE_OBP_CONSUMER_KEY
const consumerSecret = process.env.VITE_OBP_CONSUMER_SECRET
const redirectUrl = process.env.VITE_OBP_REDIRECT_URL
this.oauth = new oauth.OAuth(
apiHost + '/oauth/initiate',
apiHost + '/oauth/token',
consumerKey,
consumerSecret,
'1.0',
redirectUrl,
'HMAC-SHA1'
)
}
getConsumer(): oauth.OAuth {
return this.oauth
}
}

View File

@ -2,13 +2,13 @@
"compilerOptions": {
"module": "commonjs",
"esModuleInterop": true,
"target": "ES6",
"target": "ES2020",
"outDir": "dist-server",
"rootDir": "server",
"resolveJsonModule": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowJs": true,
"allowJs": true
},
"exclude": ["src", "server/test"],
"include": ["server"]