n8n-nodes-oidc-login 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,108 @@
1
+ # n8n-nodes-oidc-login
2
+
3
+ Community-Node-Package für einen **OIDC-Login-Flow, der von einem eingehenden Webhook/Web-Formular
4
+ ausgelöst wird** und dessen Access Token danach in nachfolgenden HTTP-Request-Nodes per eigenem
5
+ Credential-Typ genutzt werden kann.
6
+
7
+ ## Enthaltene Bausteine
8
+
9
+ | Datei | Zweck |
10
+ |---|---|
11
+ | `credentials/OidcProviderApi.credentials.ts` | Statische Provider-Settings (Client ID/Secret, Authorization-/Token-URL). Wird **vom Trigger-Node** gelesen. |
12
+ | `nodes/OidcLoginTrigger/OidcLoginTrigger.node.ts` | Webhook-Trigger mit zwei Endpunkten: Login-Start (Redirect zum OIDC-Server) und Callback (Code-Exchange). |
13
+ | `credentials/OidcBearerApi.credentials.ts` | Credential mit Feld `Access Token`, **wählbar in jedem HTTP Request Node**. Wert wird per Expression pro Ausführung befüllt. |
14
+
15
+ ## Antwortverhalten des Callback-Webhooks
16
+
17
+ Der Callback-Endpunkt (`.../oidc-login/callback`) nutzt `responseMode: 'lastNode'`: Der Browser/Client,
18
+ der nach dem Gitea-Login auf diese URL zurückgeleitet wird, bekommt **keine sofortige "Login erfolgreich"-Seite**,
19
+ sondern n8n lässt die Verbindung offen, bis der komplette Workflow durchgelaufen ist, und schickt dann
20
+ automatisch die Ausgabe der **letzten Node** im Workflow als JSON-Antwort zurück. Baut man z. B. einen
21
+ HTTP-Request-Node (Repos abrufen) + Set-Node (Formatierung) dahinter, landet exakt deren Output als
22
+ JSON-Antwort beim aufrufenden Client.
23
+
24
+ Für Fehlerfälle (ungültiger `state`, Token-Austausch fehlgeschlagen) wird weiterhin sofort und manuell
25
+ geantwortet (HTTP 400/502), da der Workflow dort nicht weiterläuft.
26
+
27
+ ## Warum zwei getrennte Credential-Typen?
28
+
29
+ - `OidcProviderApi` ist **statisch** (Client ID/Secret ändern sich nicht) → normale, fest gespeicherte Credential.
30
+ - `OidcBearerApi` ist **dynamisch pro Endnutzer/Ausführung** — n8n löst Expressions in Credential-Feldern
31
+ innerhalb des Kontexts der jeweiligen Workflow-Ausführung auf. Damit bekommt jeder Website-Besucher
32
+ in seiner eigenen Ausführung automatisch seinen eigenen, frisch erhaltenen Token — ohne dass Tokens
33
+ zwischen Nutzern/Ausführungen vermischt werden.
34
+
35
+ ## Ablauf
36
+
37
+ 1. Endnutzer ruft `https://<n8n>/webhook/oidc-login` auf (z. B. Link/Redirect aus einem Formular).
38
+ 2. `OidcLoginTrigger` generiert `state` + PKCE `code_verifier`/`code_challenge`, merkt sich beides
39
+ kurzzeitig in Workflow-Static-Data und leitet (HTTP 302) zur `authorizationUrl` des OIDC-Servers weiter.
40
+ 3. Nutzer meldet sich beim Auth-Server an. Dieser leitet zurück auf
41
+ `https://<n8n>/webhook/oidc-login/callback?code=...&state=...`.
42
+ 4. `OidcLoginTrigger` verifiziert `state`, tauscht `code` gegen Access Token (`POST` an `tokenUrl`,
43
+ inkl. `code_verifier`) und gibt den Token-Response-JSON (`access_token`, `id_token`, `expires_in`, …)
44
+ als Workflow-Output aus. Der Browser bekommt eine einfache "Anmeldung erfolgreich"-Seite.
45
+ 5. Alle nachfolgenden Nodes im Workflow haben Zugriff auf `$json.access_token`.
46
+
47
+ ## Einrichtung mit Gitea als OIDC-Server
48
+
49
+ Gitea selbst als Auth-Server nutzt feste, nicht per Discovery konfigurierbare Endpunkte:
50
+
51
+ - Authorization: `https://<gitea>/login/oauth/authorize`
52
+ - Token: `https://<gitea>/login/oauth/access_token`
53
+ - Userinfo (optional, für Profildaten): `https://<gitea>/login/oauth/userinfo`
54
+
55
+ Schritte in Gitea:
56
+
57
+ 1. Als der Nutzer, unter dem die App laufen soll (oder als Admin unter *Site Administration →
58
+ Applications* für eine instanzweite OAuth2-App): **Settings → Applications → "Create a new OAuth2 Application"**.
59
+ 2. **Application Name** vergeben, als **Redirect URI** exakt die Callback-URL des `OidcLoginTrigger`
60
+ eintragen (z. B. `https://<n8n>/webhook/oidc-login/callback`) — muss zeichengenau übereinstimmen,
61
+ sonst meldet Gitea `redirect_uri_mismatch`.
62
+ 3. Gitea zeigt **Client ID** und **Client Secret** an (Secret nur einmal sichtbar!) → in die
63
+ `OIDC Provider`-Credential in n8n eintragen.
64
+ 4. **Scope**: `openid profile email` funktioniert bei Gitea für den OIDC-Login. Für Zugriff auf die
65
+ Gitea-API selbst (z. B. Repos lesen) ggf. zusätzlich Gitea-spezifische Scopes wie `read:repository`
66
+ ergänzen (Feld "Scope" im `OidcLoginTrigger`-Node), siehe Gitea-Doku zu "granular scopes".
67
+ 5. Optional: ein nachgeschalteter HTTP-Request-Node auf `https://<gitea>/login/oauth/userinfo` (GET,
68
+ Credential = `OIDC Bearer Token`) liefert die Profildaten des eingeloggten Gitea-Nutzers — guter Test,
69
+ ob der Flow funktioniert.
70
+
71
+ ## Einrichtung (allgemein)
72
+
73
+ ```bash
74
+ npm install
75
+ npm run build
76
+ npm link
77
+ # im n8n Custom-Extensions-Ordner:
78
+ npm link n8n-nodes-oidc-login
79
+ ```
80
+
81
+ n8n neu starten. Danach:
82
+
83
+ 1. **Credential "OIDC Provider"** anlegen: Authorization URL, Token URL, Client ID, (optional) Client Secret
84
+ eures OIDC-Servers eintragen.
85
+ 2. **Node "OIDC Login Trigger"** in den Workflow ziehen, die Credential auswählen, `Path` setzen
86
+ (z. B. `oidc-login`). n8n zeigt dir automatisch zwei Webhook-URLs an (Start + `.../callback`).
87
+ 3. Beim OIDC-Provider den **Callback-Webhook** (`.../oidc-login/callback`) als **Redirect URI** des Clients
88
+ registrieren.
89
+ 4. **Credential "OIDC Bearer Token"** anlegen: beim Feld "Access Token" auf **Expression** umschalten
90
+ und z. B. `={{ $json.access_token }}` eintragen (oder `={{ $('OIDC Login Trigger').item.json.access_token }}`,
91
+ falls dazwischen weitere Nodes liegen).
92
+ 5. Nachfolgenden **HTTP Request Node** hinzufügen → Authentication → "Predefined Credential Type" →
93
+ "OIDC Bearer Token" auswählen. Der `Authorization: Bearer <token>`-Header wird automatisch gesetzt.
94
+
95
+ ## Wichtige Einschränkungen / Hinweise
96
+
97
+ - **Ein Token gilt nur für die Ausführung, in der er erzeugt wurde.** Das ist hier so gewollt
98
+ (jeder Endnutzer meldet sich selbst an), aber es bedeutet auch: Der Token wird nicht dauerhaft
99
+ gespeichert oder automatisch erneuert (kein Refresh-Token-Handling in diesem Minimalbeispiel).
100
+ Für Refresh-Logik müsste `refresh_token` aus `tokenResponse` zusätzlich ausgewertet und ein eigener
101
+ Refresh-Aufruf ergänzt werden.
102
+ - **State-Speicherung** erfolgt über `this.getWorkflowStaticData('node')` (persistiert in der n8n-DB,
103
+ TTL 10 Minuten). Für sehr hohe Login-Frequenzen ggf. auf eine externe Datenbank/Cache umstellen.
104
+ - **Sicherheit**: PKCE (S256) wird immer verwendet; Client Secret ist optional (für "public" Clients).
105
+ Stellt sicher, dass die n8n-Instanz per HTTPS erreichbar ist, da sensible Tokens durchlaufen.
106
+ - Der HTTP Request Node muss in **derselben Workflow-Ausführung** wie der Login-Callback laufen
107
+ (direkt oder indirekt nachgeschaltet) — die Credential kann keinen Token aus einer anderen,
108
+ bereits abgeschlossenen Ausführung "nachladen".
@@ -0,0 +1,22 @@
1
+ import { ICredentialType, INodeProperties, IAuthenticateGeneric } from 'n8n-workflow';
2
+ /**
3
+ * Credential für nachfolgende HTTP-Request-Nodes.
4
+ *
5
+ * WICHTIG: "Access Token" muss im Credential-UI über den kleinen
6
+ * Expression-Schalter (⚡/"Add Expression") auf einen Ausdruck umgestellt
7
+ * werden, z. B.:
8
+ * ={{ $json.access_token }}
9
+ * oder, falls zwischen Trigger und HTTP-Request-Node weitere Nodes liegen:
10
+ * ={{ $('OIDC Login Trigger').item.json.access_token }}
11
+ *
12
+ * n8n löst Expressions in Credential-Feldern pro Workflow-AUSFÜHRUNG auf
13
+ * (nicht global), d. h. jeder Endnutzer bekommt in seiner eigenen
14
+ * Ausführung automatisch seinen eigenen, gerade erhaltenen Token.
15
+ */
16
+ export declare class OidcBearerApi implements ICredentialType {
17
+ name: string;
18
+ displayName: string;
19
+ documentationUrl: string;
20
+ properties: INodeProperties[];
21
+ authenticate: IAuthenticateGeneric;
22
+ }
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OidcBearerApi = void 0;
4
+ /**
5
+ * Credential für nachfolgende HTTP-Request-Nodes.
6
+ *
7
+ * WICHTIG: "Access Token" muss im Credential-UI über den kleinen
8
+ * Expression-Schalter (⚡/"Add Expression") auf einen Ausdruck umgestellt
9
+ * werden, z. B.:
10
+ * ={{ $json.access_token }}
11
+ * oder, falls zwischen Trigger und HTTP-Request-Node weitere Nodes liegen:
12
+ * ={{ $('OIDC Login Trigger').item.json.access_token }}
13
+ *
14
+ * n8n löst Expressions in Credential-Feldern pro Workflow-AUSFÜHRUNG auf
15
+ * (nicht global), d. h. jeder Endnutzer bekommt in seiner eigenen
16
+ * Ausführung automatisch seinen eigenen, gerade erhaltenen Token.
17
+ */
18
+ class OidcBearerApi {
19
+ constructor() {
20
+ this.name = 'oidcBearerApi';
21
+ this.displayName = 'OIDC Bearer Token';
22
+ this.documentationUrl = 'https://docs.n8n.io/code/expressions/';
23
+ this.properties = [
24
+ {
25
+ displayName: 'Access Token',
26
+ name: 'accessToken',
27
+ type: 'string',
28
+ typeOptions: { password: true },
29
+ default: '',
30
+ description: 'In der Regel als Expression setzen, z. B. ={{ $json.access_token }} — verweist auf den vom OIDC Login Trigger in DIESER Ausführung erhaltenen Token.',
31
+ },
32
+ ];
33
+ this.authenticate = {
34
+ type: 'generic',
35
+ properties: {
36
+ headers: {
37
+ Authorization: '=Bearer {{$credentials.accessToken}}',
38
+ },
39
+ },
40
+ };
41
+ }
42
+ }
43
+ exports.OidcBearerApi = OidcBearerApi;
@@ -0,0 +1,12 @@
1
+ import { ICredentialType, INodeProperties } from 'n8n-workflow';
2
+ /**
3
+ * Statische Konfiguration des OIDC-Providers. Wird ausschließlich vom
4
+ * OidcLoginTrigger-Node gelesen (per this.getCredentials), NICHT für
5
+ * "Generic Auth" an HTTP-Request-Nodes gedacht.
6
+ */
7
+ export declare class OidcProviderApi implements ICredentialType {
8
+ name: string;
9
+ displayName: string;
10
+ documentationUrl: string;
11
+ properties: INodeProperties[];
12
+ }
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OidcProviderApi = void 0;
4
+ /**
5
+ * Statische Konfiguration des OIDC-Providers. Wird ausschließlich vom
6
+ * OidcLoginTrigger-Node gelesen (per this.getCredentials), NICHT für
7
+ * "Generic Auth" an HTTP-Request-Nodes gedacht.
8
+ */
9
+ class OidcProviderApi {
10
+ constructor() {
11
+ this.name = 'oidcProviderApi';
12
+ this.displayName = 'OIDC Provider';
13
+ this.documentationUrl = 'https://openid.net/specs/openid-connect-core-1_0.html';
14
+ this.properties = [
15
+ {
16
+ displayName: 'Authorization URL',
17
+ name: 'authorizationUrl',
18
+ type: 'string',
19
+ default: 'https://gitea.example.com/login/oauth/authorize',
20
+ description: 'Bei Gitea fest: <Gitea-URL>/login/oauth/authorize',
21
+ },
22
+ {
23
+ displayName: 'Token URL',
24
+ name: 'tokenUrl',
25
+ type: 'string',
26
+ default: 'https://gitea.example.com/login/oauth/access_token',
27
+ description: 'Bei Gitea fest: <Gitea-URL>/login/oauth/access_token',
28
+ },
29
+ {
30
+ displayName: 'Client ID',
31
+ name: 'clientId',
32
+ type: 'string',
33
+ default: '',
34
+ required: true,
35
+ },
36
+ {
37
+ displayName: 'Client Secret',
38
+ name: 'clientSecret',
39
+ type: 'string',
40
+ typeOptions: { password: true },
41
+ default: '',
42
+ description: 'Leer lassen, falls der Client als "public" (nur PKCE, kein Secret) registriert ist',
43
+ },
44
+ ];
45
+ }
46
+ }
47
+ exports.OidcProviderApi = OidcProviderApi;
@@ -0,0 +1,5 @@
1
+ import { IHookFunctions, IWebhookFunctions, INodeType, INodeTypeDescription, IWebhookResponseData } from 'n8n-workflow';
2
+ export declare class OidcLoginTrigger implements INodeType {
3
+ description: INodeTypeDescription;
4
+ webhook(this: IHookFunctions & IWebhookFunctions): Promise<IWebhookResponseData>;
5
+ }
@@ -0,0 +1,180 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.OidcLoginTrigger = void 0;
37
+ const crypto = __importStar(require("node:crypto"));
38
+ const n8n_workflow_1 = require("n8n-workflow");
39
+ const PENDING_TTL_MS = 10 * 60 * 1000; // 10 Minuten
40
+ function base64url(input) {
41
+ return input.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
42
+ }
43
+ class OidcLoginTrigger {
44
+ constructor() {
45
+ this.description = {
46
+ displayName: 'OIDC Login Trigger',
47
+ name: 'oidcLoginTrigger',
48
+ icon: 'fa:sign-in-alt',
49
+ group: ['trigger'],
50
+ version: 1,
51
+ description: 'Leitet einen eingehenden Webhook-/Formular-Request zum OIDC-Login weiter und stellt danach den Access Token als Workflow-Output bereit',
52
+ defaults: { name: 'OIDC Login Trigger' },
53
+ inputs: [],
54
+ outputs: [n8n_workflow_1.NodeConnectionTypes.Main],
55
+ credentials: [
56
+ {
57
+ name: 'oidcProviderApi',
58
+ required: true,
59
+ },
60
+ ],
61
+ webhooks: [
62
+ {
63
+ name: 'default',
64
+ httpMethod: 'GET',
65
+ responseMode: 'onReceived',
66
+ path: '={{$parameter["path"]}}',
67
+ },
68
+ {
69
+ name: 'callback',
70
+ httpMethod: 'GET',
71
+ // 'lastNode': n8n wartet mit der HTTP-Antwort, bis der gesamte Workflow
72
+ // durchgelaufen ist, und sendet dann automatisch die Ausgabe der letzten
73
+ // Node (hier: die formatierte Repo-Liste) als JSON-Response zurück.
74
+ responseMode: 'lastNode',
75
+ path: '={{$parameter["path"]}}/callback',
76
+ },
77
+ ],
78
+ properties: [
79
+ {
80
+ displayName: 'Path',
81
+ name: 'path',
82
+ type: 'string',
83
+ default: 'oidc-login',
84
+ description: 'Basis-Pfad des Login-Webhooks. Der Callback läuft automatisch unter <Pfad>/callback — diese URL muss beim OIDC-Provider als Redirect URI hinterlegt werden.',
85
+ },
86
+ {
87
+ displayName: 'Scope',
88
+ name: 'scope',
89
+ type: 'string',
90
+ default: 'openid profile email',
91
+ },
92
+ ],
93
+ };
94
+ }
95
+ async webhook() {
96
+ var _a;
97
+ const webhookName = this.getWebhookName();
98
+ const req = this.getRequestObject();
99
+ const res = this.getResponseObject();
100
+ const credentials = (await this.getCredentials('oidcProviderApi'));
101
+ const staticData = this.getWorkflowStaticData('node');
102
+ if (!staticData.pending) {
103
+ staticData.pending = {};
104
+ }
105
+ // Abgelaufene Login-Versuche aufräumen
106
+ const now = Date.now();
107
+ for (const [key, value] of Object.entries(staticData.pending)) {
108
+ if (now - value.createdAt > PENDING_TTL_MS) {
109
+ delete staticData.pending[key];
110
+ }
111
+ }
112
+ if (webhookName === 'default') {
113
+ // --- Schritt 1: eingehender Request -> Redirect zum OIDC-Server ---
114
+ const state = crypto.randomBytes(16).toString('hex');
115
+ const codeVerifier = base64url(crypto.randomBytes(32));
116
+ const codeChallenge = base64url(crypto.createHash('sha256').update(codeVerifier).digest());
117
+ staticData.pending[state] = { codeVerifier, createdAt: now };
118
+ const redirectUri = this.getNodeWebhookUrl('callback');
119
+ const scope = this.getNodeParameter('scope') || 'openid profile email';
120
+ const authUrl = new URL(credentials.authorizationUrl);
121
+ authUrl.searchParams.set('response_type', 'code');
122
+ authUrl.searchParams.set('client_id', credentials.clientId);
123
+ authUrl.searchParams.set('redirect_uri', redirectUri);
124
+ authUrl.searchParams.set('scope', scope);
125
+ authUrl.searchParams.set('state', state);
126
+ authUrl.searchParams.set('code_challenge', codeChallenge);
127
+ authUrl.searchParams.set('code_challenge_method', 'S256');
128
+ res.writeHead(302, { Location: authUrl.toString() });
129
+ res.end();
130
+ // Workflow läuft für diesen Request NICHT weiter - erst der Callback tut das
131
+ return { noWebhookResponse: true };
132
+ }
133
+ // --- Schritt 2: Callback vom OIDC-Server ---
134
+ const query = req.query;
135
+ if (query.error) {
136
+ res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });
137
+ res.end(`<h1>Anmeldung fehlgeschlagen</h1><p>${(_a = query.error_description) !== null && _a !== void 0 ? _a : query.error}</p>`);
138
+ return { noWebhookResponse: true };
139
+ }
140
+ const pending = query.state ? staticData.pending[query.state] : undefined;
141
+ if (!query.code || !query.state || !pending) {
142
+ res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });
143
+ res.end('<h1>Ungültige oder abgelaufene Login-Anfrage</h1>');
144
+ return { noWebhookResponse: true };
145
+ }
146
+ delete staticData.pending[query.state];
147
+ const redirectUri = this.getNodeWebhookUrl('callback');
148
+ let tokenResponse;
149
+ try {
150
+ tokenResponse = (await this.helpers.httpRequest({
151
+ method: 'POST',
152
+ url: credentials.tokenUrl,
153
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
154
+ body: new URLSearchParams({
155
+ grant_type: 'authorization_code',
156
+ code: query.code,
157
+ redirect_uri: redirectUri,
158
+ client_id: credentials.clientId,
159
+ ...(credentials.clientSecret ? { client_secret: credentials.clientSecret } : {}),
160
+ code_verifier: pending.codeVerifier,
161
+ }).toString(),
162
+ json: true,
163
+ }));
164
+ }
165
+ catch (error) {
166
+ res.writeHead(502, { 'Content-Type': 'text/html; charset=utf-8' });
167
+ res.end('<h1>Token-Austausch mit dem OIDC-Server fehlgeschlagen</h1>');
168
+ throw error;
169
+ }
170
+ // KEINE manuelle Antwort hier: responseMode 'lastNode' (siehe description.webhooks)
171
+ // sorgt dafür, dass n8n automatisch antwortet, sobald der Workflow durchgelaufen
172
+ // ist - mit der Ausgabe der letzten Node (z. B. die formatierte Repo-Liste) als JSON.
173
+ // tokenResponse (access_token, id_token, ...) steht nachfolgenden Nodes als $json
174
+ // zur Verfügung.
175
+ return {
176
+ workflowData: [[{ json: tokenResponse }]],
177
+ };
178
+ }
179
+ }
180
+ exports.OidcLoginTrigger = OidcLoginTrigger;
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "n8n-nodes-oidc-login",
3
+ "version": "0.1.0",
4
+ "description": "Custom credentials + web node for n8n",
5
+ "keywords": [
6
+ "n8n-community-node-package"
7
+ ],
8
+ "license": "MIT",
9
+ "main": "index.js",
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "n8n": {
14
+ "n8nNodesApiVersion": 1,
15
+ "credentials": [
16
+ "dist/credentials/OidcProviderApi.credentials.js",
17
+ "dist/credentials/OidcBearerApi.credentials.js"
18
+ ],
19
+ "nodes": [
20
+ "dist/nodes/OidcLoginTrigger/OidcLoginTrigger.node.js"
21
+ ]
22
+ },
23
+ "scripts": {
24
+ "build": "tsc -p tsconfig.json",
25
+ "dev": "tsc -p tsconfig.json --watch"
26
+ },
27
+ "devDependencies": {
28
+ "@types/node": "^26.4.0",
29
+ "n8n-workflow": "^1.60.0",
30
+ "typescript": "^5.4.0"
31
+ }
32
+ }