@caronte-sdk/node 0.2.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 +198 -0
- package/dist/adapters/apollo.cjs +71 -0
- package/dist/adapters/apollo.cjs.map +1 -0
- package/dist/adapters/apollo.d.cts +34 -0
- package/dist/adapters/apollo.d.ts +34 -0
- package/dist/adapters/apollo.js +45 -0
- package/dist/adapters/apollo.js.map +1 -0
- package/dist/adapters/express.cjs +57 -0
- package/dist/adapters/express.cjs.map +1 -0
- package/dist/adapters/express.d.cts +19 -0
- package/dist/adapters/express.d.ts +19 -0
- package/dist/adapters/express.js +41 -0
- package/dist/adapters/express.js.map +1 -0
- package/dist/adapters/fastify.cjs +66 -0
- package/dist/adapters/fastify.cjs.map +1 -0
- package/dist/adapters/fastify.d.cts +19 -0
- package/dist/adapters/fastify.d.ts +19 -0
- package/dist/adapters/fastify.js +42 -0
- package/dist/adapters/fastify.js.map +1 -0
- package/dist/adapters/nest/index.cjs +299 -0
- package/dist/adapters/nest/index.cjs.map +1 -0
- package/dist/adapters/nest/index.d.cts +60 -0
- package/dist/adapters/nest/index.d.ts +60 -0
- package/dist/adapters/nest/index.js +109 -0
- package/dist/adapters/nest/index.js.map +1 -0
- package/dist/chunk-AKUM2UEO.js +21 -0
- package/dist/chunk-AKUM2UEO.js.map +1 -0
- package/dist/chunk-G73YTEJQ.js +146 -0
- package/dist/chunk-G73YTEJQ.js.map +1 -0
- package/dist/chunk-HV6X5RJY.js +37 -0
- package/dist/chunk-HV6X5RJY.js.map +1 -0
- package/dist/client-Cgm1csuM.d.cts +49 -0
- package/dist/client-Cgm1csuM.d.ts +49 -0
- package/dist/index.cjs +199 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +27 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/package.json +78 -0
package/README.md
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# @argos/node
|
|
2
|
+
|
|
3
|
+
Node.js client for the **Argos** authorizer — handles app authentication,
|
|
4
|
+
JWT validation, permission checking and automatic operation sync.
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm install @argos/node
|
|
10
|
+
# With framework-specific adapter
|
|
11
|
+
npm install @argos/node express # Express
|
|
12
|
+
npm install @argos/node fastify # Fastify
|
|
13
|
+
npm install @argos/node @nestjs/common # NestJS
|
|
14
|
+
npm install @argos/node @apollo/server # Apollo Server
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick start
|
|
18
|
+
|
|
19
|
+
### Express
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import express from 'express';
|
|
23
|
+
import { ArgosClient, getRegistry } from '@argos/node';
|
|
24
|
+
import { argos } from '@argos/node/express';
|
|
25
|
+
|
|
26
|
+
const client = new ArgosClient({
|
|
27
|
+
authorizerUrl: process.env.AUTHORIZER_URL!,
|
|
28
|
+
realmId: process.env.REALM_ID!,
|
|
29
|
+
appId: process.env.APP_ID!,
|
|
30
|
+
secret: process.env.APP_SECRET!,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const { middleware, guard } = argos(client);
|
|
34
|
+
|
|
35
|
+
const app = express();
|
|
36
|
+
app.use(express.json(), middleware);
|
|
37
|
+
|
|
38
|
+
app.get('/health', guard('health:check', 'public'), (_req, res) => res.json({ status: 'ok' }));
|
|
39
|
+
app.get('/tasks', guard('tasks:list', 'private'), (_req, res) => res.json(tasks));
|
|
40
|
+
app.post('/tasks', guard('tasks:create', 'protected'), (req, res) => { /* req.argosUser */ });
|
|
41
|
+
|
|
42
|
+
for (const op of getRegistry()) client.registerOperation(op);
|
|
43
|
+
await client.startup();
|
|
44
|
+
|
|
45
|
+
app.listen(3001);
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Fastify
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
import Fastify from 'fastify';
|
|
52
|
+
import { ArgosClient, getRegistry } from '@argos/node';
|
|
53
|
+
import { argosFastifyPlugin } from '@argos/node/fastify';
|
|
54
|
+
|
|
55
|
+
const client = new ArgosClient({ ... });
|
|
56
|
+
const fastify = Fastify();
|
|
57
|
+
|
|
58
|
+
await fastify.register(argosFastifyPlugin, { client });
|
|
59
|
+
|
|
60
|
+
fastify.get('/health',
|
|
61
|
+
{ preHandler: fastify.argosGuard('health:check', 'public') },
|
|
62
|
+
async () => ({ status: 'ok' }),
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
fastify.get('/tasks',
|
|
66
|
+
{ preHandler: fastify.argosGuard('tasks:list', 'private') },
|
|
67
|
+
async (req) => { /* req.argosUser */ },
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
for (const op of getRegistry()) client.registerOperation(op);
|
|
71
|
+
await client.startup();
|
|
72
|
+
|
|
73
|
+
await fastify.listen({ port: 3002 });
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### NestJS
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
// app.module.ts
|
|
80
|
+
import { Module } from '@nestjs/common';
|
|
81
|
+
import { ArgosModule } from '@argos/node/nest';
|
|
82
|
+
|
|
83
|
+
@Module({
|
|
84
|
+
imports: [
|
|
85
|
+
ArgosModule.forRootAsync({
|
|
86
|
+
authorizerUrl: process.env.AUTHORIZER_URL!,
|
|
87
|
+
realmId: process.env.REALM_ID!,
|
|
88
|
+
appId: process.env.APP_ID!,
|
|
89
|
+
secret: process.env.APP_SECRET!,
|
|
90
|
+
}),
|
|
91
|
+
],
|
|
92
|
+
})
|
|
93
|
+
export class AppModule {}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
// tasks.controller.ts
|
|
98
|
+
import { Controller, Get, Post, UseGuards } from '@nestjs/common';
|
|
99
|
+
import { ArgosGuard, ArgosUser, Operation } from '@argos/node/nest';
|
|
100
|
+
import type { TokenClaims } from '@argos/node';
|
|
101
|
+
|
|
102
|
+
@Controller('tasks')
|
|
103
|
+
@UseGuards(ArgosGuard)
|
|
104
|
+
export class TasksController {
|
|
105
|
+
|
|
106
|
+
@Get()
|
|
107
|
+
@Operation('tasks:list', 'private')
|
|
108
|
+
list() { return tasks; }
|
|
109
|
+
|
|
110
|
+
@Post()
|
|
111
|
+
@Operation('tasks:create', 'protected')
|
|
112
|
+
create(@ArgosUser() user: TokenClaims) {
|
|
113
|
+
// user.sub, user.groups
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Apollo Server
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
import { ApolloServer } from '@apollo/server';
|
|
122
|
+
import { ArgosClient, getRegistry } from '@argos/node';
|
|
123
|
+
import { argosPlugin, createGuard, type ArgosContext } from '@argos/node/apollo';
|
|
124
|
+
|
|
125
|
+
const client = new ArgosClient({ ... });
|
|
126
|
+
const guard = createGuard(client);
|
|
127
|
+
|
|
128
|
+
const resolvers = {
|
|
129
|
+
Query: {
|
|
130
|
+
health: guard('health:check', 'public', () => 'ok'),
|
|
131
|
+
tasks: guard('tasks:list', 'private', (_p, _a, ctx: ArgosContext) => {
|
|
132
|
+
const user = ctx.argosUser; // TokenClaims
|
|
133
|
+
return tasks;
|
|
134
|
+
}),
|
|
135
|
+
},
|
|
136
|
+
Mutation: {
|
|
137
|
+
createTask: guard('tasks:create', 'protected', (_p, { input }, ctx: ArgosContext) => {
|
|
138
|
+
// ctx.argosUser is the authenticated user
|
|
139
|
+
}),
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
for (const op of getRegistry()) client.registerOperation(op);
|
|
144
|
+
await client.startup();
|
|
145
|
+
|
|
146
|
+
const server = new ApolloServer<ArgosContext>({
|
|
147
|
+
typeDefs,
|
|
148
|
+
resolvers,
|
|
149
|
+
plugins: [argosPlugin(client)],
|
|
150
|
+
});
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Operation levels
|
|
154
|
+
|
|
155
|
+
| Level | Who can access |
|
|
156
|
+
|-------------|----------------|
|
|
157
|
+
| `public` | Everyone — no token required |
|
|
158
|
+
| `private` | Any authenticated user (token with at least one group) |
|
|
159
|
+
| `protected` | Only users whose groups intersect the operation's `allowed_groups` |
|
|
160
|
+
|
|
161
|
+
## Method auto-detection
|
|
162
|
+
|
|
163
|
+
The method is inferred from the last segment of the operation id:
|
|
164
|
+
|
|
165
|
+
| Operation id suffix | Detected method |
|
|
166
|
+
|---------------------|-----------------|
|
|
167
|
+
| `list`, `get`, `fetch`, `read` | `read` |
|
|
168
|
+
| `delete`, `remove`, `destroy` | `delete` |
|
|
169
|
+
| `stream`, `subscribe`, `watch`, `listen` | `stream` |
|
|
170
|
+
| anything else | `write` |
|
|
171
|
+
|
|
172
|
+
You can always pass the method explicitly as the third argument to `guard()`.
|
|
173
|
+
|
|
174
|
+
## Configuration
|
|
175
|
+
|
|
176
|
+
| Parameter | Description |
|
|
177
|
+
|----------------|-------------|
|
|
178
|
+
| `authorizerUrl` | Base URL of Argos, including the API prefix (e.g. `http://host/api`) |
|
|
179
|
+
| `realmId` | Name or UUID of the realm (e.g. `purp`) |
|
|
180
|
+
| `appId` | UUID of the app registered in `auth.apps` |
|
|
181
|
+
| `secret` | Plain-text app secret — use env vars, never commit |
|
|
182
|
+
|
|
183
|
+
## How it works
|
|
184
|
+
|
|
185
|
+
On `startup()`, the client:
|
|
186
|
+
1. **Authenticates** — exchanges `appId` + `secret` for a short-lived app JWT
|
|
187
|
+
2. **Fetches JWKS** — caches the public keys for token validation
|
|
188
|
+
3. **Syncs operations** — pushes the registered operation catalogue to the authorizer
|
|
189
|
+
4. **Fetches operations** — retrieves the authorised operation list with group bindings
|
|
190
|
+
|
|
191
|
+
On each request, the adapter:
|
|
192
|
+
- Validates the Bearer token using the cached JWKS
|
|
193
|
+
- Checks `TokenClaims.groups` against the operation's `allowedGroups`
|
|
194
|
+
- Injects `argosUser` (`TokenClaims`) into the request context
|
|
195
|
+
|
|
196
|
+
## License
|
|
197
|
+
|
|
198
|
+
CC0 1.0 Universal — public domain.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/exceptions.ts
|
|
4
|
+
var ArgosError = class extends Error {
|
|
5
|
+
constructor(message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = this.constructor.name;
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
var ArgosTokenError = class extends ArgosError {
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// src/registry.ts
|
|
14
|
+
var _registry = [];
|
|
15
|
+
function detectMethod(id) {
|
|
16
|
+
const last = (id.split(":").pop() ?? "").toLowerCase();
|
|
17
|
+
if (/^(get|list|fetch|read)/.test(last)) return "read";
|
|
18
|
+
if (/^(delete|remove|destroy)/.test(last)) return "delete";
|
|
19
|
+
if (/(stream|subscribe|watch|listen)/.test(last)) return "stream";
|
|
20
|
+
return "write";
|
|
21
|
+
}
|
|
22
|
+
function registerOperation(op) {
|
|
23
|
+
const exists = _registry.some(
|
|
24
|
+
(o) => o.identifier === op.identifier && o.method === op.method
|
|
25
|
+
);
|
|
26
|
+
if (!exists) _registry.push(op);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// src/adapters/apollo.ts
|
|
30
|
+
function argosPlugin(client) {
|
|
31
|
+
return {
|
|
32
|
+
async requestDidStart() {
|
|
33
|
+
return {
|
|
34
|
+
async didResolveOperation(ctx) {
|
|
35
|
+
const httpAuth = ctx.request.http?.headers.get("authorization") ?? "";
|
|
36
|
+
const wsAuth = ctx.contextValue?.connectionParams?.Authorization ?? "";
|
|
37
|
+
const auth = httpAuth || wsAuth;
|
|
38
|
+
const [scheme, token] = auth.split(" ");
|
|
39
|
+
if (scheme?.toLowerCase() === "bearer" && token) {
|
|
40
|
+
try {
|
|
41
|
+
ctx.contextValue.argosUser = await client.validateToken(token);
|
|
42
|
+
} catch (err) {
|
|
43
|
+
if (err instanceof ArgosTokenError) {
|
|
44
|
+
ctx.contextValue.argosUser = void 0;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function createGuard(client) {
|
|
54
|
+
return function guard(id, level, resolver, method) {
|
|
55
|
+
const resolvedMethod = method ?? detectMethod(id);
|
|
56
|
+
registerOperation({ identifier: id, method: resolvedMethod, level });
|
|
57
|
+
return (parent, args, context, info) => {
|
|
58
|
+
if (level === "public") return resolver(parent, args, context, info);
|
|
59
|
+
if (!context.argosUser) throw new Error("Unauthorized: Bearer token required.");
|
|
60
|
+
if (!client.checkPermission(context.argosUser, id, resolvedMethod)) {
|
|
61
|
+
throw new Error("Forbidden: Insufficient permissions.");
|
|
62
|
+
}
|
|
63
|
+
return resolver(parent, args, context, info);
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
exports.argosPlugin = argosPlugin;
|
|
69
|
+
exports.createGuard = createGuard;
|
|
70
|
+
//# sourceMappingURL=apollo.cjs.map
|
|
71
|
+
//# sourceMappingURL=apollo.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/exceptions.ts","../../src/registry.ts","../../src/adapters/apollo.ts"],"names":[],"mappings":"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","file":"apollo.cjs","sourcesContent":["export class ArgosError extends Error {\n constructor(message: string) {\n super(message);\n this.name = this.constructor.name;\n }\n}\n\nexport class ArgosAuthError extends ArgosError {}\nexport class ArgosTokenError extends ArgosError {}\nexport class ArgosForbiddenError extends ArgosError {}\nexport class ArgosSyncError extends ArgosError {}\nexport class ArgosConfigError extends ArgosError {}","import type { OperationDescriptor } from './models.js';\n\nconst _registry: OperationDescriptor[] = [];\n\n/** Infer operation method from the last segment of the operation id.\n * e.g. \"tasks:list\" → \"read\", \"tasks:create\" → \"write\", \"tasks:delete\" → \"delete\"\n */\nexport function detectMethod(id: string): string {\n const last = (id.split(':').pop() ?? '').toLowerCase();\n if (/^(get|list|fetch|read)/.test(last)) return 'read';\n if (/^(delete|remove|destroy)/.test(last)) return 'delete';\n if (/(stream|subscribe|watch|listen)/.test(last)) return 'stream';\n return 'write';\n}\n\n/** Register an operation in the global registry (used by startup to sync). */\nexport function registerOperation(op: OperationDescriptor): void {\n const exists = _registry.some(\n o => o.identifier === op.identifier && o.method === op.method,\n );\n if (!exists) _registry.push(op);\n}\n\nexport function getRegistry(): OperationDescriptor[] {\n return [..._registry];\n}\n\nexport function clearRegistry(): void {\n _registry.length = 0;\n}","import type { ApolloServerPlugin, BaseContext, GraphQLRequestContext } from '@apollo/server';\nimport { ArgosClient } from '../client.js';\nimport { ArgosTokenError } from '../exceptions.js';\nimport type { TokenClaims } from '../models.js';\nimport { detectMethod, registerOperation } from '../registry.js';\n\nexport interface ArgosContext extends BaseContext {\n argosUser?: TokenClaims;\n}\n\n/**\n * Apollo Server plugin for argos.\n *\n * Validates the Bearer token on every request and stores the claims in\n * `context.argosUser`. Use `createGuard(client)` to enforce per-resolver\n * permissions.\n */\nexport function argosPlugin(client: ArgosClient): ApolloServerPlugin<ArgosContext> {\n return {\n async requestDidStart() {\n return {\n async didResolveOperation(ctx: GraphQLRequestContext<ArgosContext>) {\n const httpAuth = ctx.request.http?.headers.get('authorization') ?? '';\n const wsAuth = ((ctx.contextValue as any)?.connectionParams?.Authorization as string | undefined) ?? '';\n const auth = httpAuth || wsAuth;\n\n const [scheme, token] = auth.split(' ');\n if (scheme?.toLowerCase() === 'bearer' && token) {\n try {\n ctx.contextValue.argosUser = await client.validateToken(token);\n } catch (err) {\n if (err instanceof ArgosTokenError) {\n ctx.contextValue.argosUser = undefined;\n }\n }\n }\n },\n };\n },\n };\n}\n\n/**\n * Factory that returns a per-resolver guard bound to a `ArgosClient`.\n *\n * @example\n * ```ts\n * const guard = createGuard(client);\n *\n * const resolvers = {\n * Query: {\n * tasks: guard('tasks:list', 'private', async (_parent, _args, context) => {\n * const user = context.argosUser;\n * return db.tasks.findAll();\n * }),\n * },\n * };\n * ```\n */\nexport function createGuard(client: ArgosClient) {\n return function guard<TParent, TArgs, TContext extends ArgosContext, TReturn>(\n id: string,\n level: string,\n resolver: (parent: TParent, args: TArgs, context: TContext, info: unknown) => TReturn,\n method?: string,\n ): (parent: TParent, args: TArgs, context: TContext, info: unknown) => TReturn {\n const resolvedMethod = method ?? detectMethod(id);\n registerOperation({ identifier: id, method: resolvedMethod, level });\n\n return (parent, args, context, info) => {\n if (level === 'public') return resolver(parent, args, context, info);\n if (!context.argosUser) throw new Error('Unauthorized: Bearer token required.');\n if (!client.checkPermission(context.argosUser, id, resolvedMethod)) {\n throw new Error('Forbidden: Insufficient permissions.');\n }\n return resolver(parent, args, context, info);\n };\n };\n}"]}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { BaseContext, ApolloServerPlugin } from '@apollo/server';
|
|
2
|
+
import { T as TokenClaims, A as ArgosClient } from '../client-Cgm1csuM.cjs';
|
|
3
|
+
|
|
4
|
+
interface ArgosContext extends BaseContext {
|
|
5
|
+
argosUser?: TokenClaims;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Apollo Server plugin for argos.
|
|
9
|
+
*
|
|
10
|
+
* Validates the Bearer token on every request and stores the claims in
|
|
11
|
+
* `context.argosUser`. Use `createGuard(client)` to enforce per-resolver
|
|
12
|
+
* permissions.
|
|
13
|
+
*/
|
|
14
|
+
declare function argosPlugin(client: ArgosClient): ApolloServerPlugin<ArgosContext>;
|
|
15
|
+
/**
|
|
16
|
+
* Factory that returns a per-resolver guard bound to a `ArgosClient`.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* const guard = createGuard(client);
|
|
21
|
+
*
|
|
22
|
+
* const resolvers = {
|
|
23
|
+
* Query: {
|
|
24
|
+
* tasks: guard('tasks:list', 'private', async (_parent, _args, context) => {
|
|
25
|
+
* const user = context.argosUser;
|
|
26
|
+
* return db.tasks.findAll();
|
|
27
|
+
* }),
|
|
28
|
+
* },
|
|
29
|
+
* };
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
declare function createGuard(client: ArgosClient): <TParent, TArgs, TContext extends ArgosContext, TReturn>(id: string, level: string, resolver: (parent: TParent, args: TArgs, context: TContext, info: unknown) => TReturn, method?: string) => (parent: TParent, args: TArgs, context: TContext, info: unknown) => TReturn;
|
|
33
|
+
|
|
34
|
+
export { type ArgosContext, argosPlugin, createGuard };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { BaseContext, ApolloServerPlugin } from '@apollo/server';
|
|
2
|
+
import { T as TokenClaims, A as ArgosClient } from '../client-Cgm1csuM.js';
|
|
3
|
+
|
|
4
|
+
interface ArgosContext extends BaseContext {
|
|
5
|
+
argosUser?: TokenClaims;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Apollo Server plugin for argos.
|
|
9
|
+
*
|
|
10
|
+
* Validates the Bearer token on every request and stores the claims in
|
|
11
|
+
* `context.argosUser`. Use `createGuard(client)` to enforce per-resolver
|
|
12
|
+
* permissions.
|
|
13
|
+
*/
|
|
14
|
+
declare function argosPlugin(client: ArgosClient): ApolloServerPlugin<ArgosContext>;
|
|
15
|
+
/**
|
|
16
|
+
* Factory that returns a per-resolver guard bound to a `ArgosClient`.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* const guard = createGuard(client);
|
|
21
|
+
*
|
|
22
|
+
* const resolvers = {
|
|
23
|
+
* Query: {
|
|
24
|
+
* tasks: guard('tasks:list', 'private', async (_parent, _args, context) => {
|
|
25
|
+
* const user = context.argosUser;
|
|
26
|
+
* return db.tasks.findAll();
|
|
27
|
+
* }),
|
|
28
|
+
* },
|
|
29
|
+
* };
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
declare function createGuard(client: ArgosClient): <TParent, TArgs, TContext extends ArgosContext, TReturn>(id: string, level: string, resolver: (parent: TParent, args: TArgs, context: TContext, info: unknown) => TReturn, method?: string) => (parent: TParent, args: TArgs, context: TContext, info: unknown) => TReturn;
|
|
33
|
+
|
|
34
|
+
export { type ArgosContext, argosPlugin, createGuard };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { ArgosTokenError } from '../chunk-AKUM2UEO.js';
|
|
2
|
+
import { detectMethod, registerOperation } from '../chunk-HV6X5RJY.js';
|
|
3
|
+
|
|
4
|
+
// src/adapters/apollo.ts
|
|
5
|
+
function argosPlugin(client) {
|
|
6
|
+
return {
|
|
7
|
+
async requestDidStart() {
|
|
8
|
+
return {
|
|
9
|
+
async didResolveOperation(ctx) {
|
|
10
|
+
const httpAuth = ctx.request.http?.headers.get("authorization") ?? "";
|
|
11
|
+
const wsAuth = ctx.contextValue?.connectionParams?.Authorization ?? "";
|
|
12
|
+
const auth = httpAuth || wsAuth;
|
|
13
|
+
const [scheme, token] = auth.split(" ");
|
|
14
|
+
if (scheme?.toLowerCase() === "bearer" && token) {
|
|
15
|
+
try {
|
|
16
|
+
ctx.contextValue.argosUser = await client.validateToken(token);
|
|
17
|
+
} catch (err) {
|
|
18
|
+
if (err instanceof ArgosTokenError) {
|
|
19
|
+
ctx.contextValue.argosUser = void 0;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function createGuard(client) {
|
|
29
|
+
return function guard(id, level, resolver, method) {
|
|
30
|
+
const resolvedMethod = method ?? detectMethod(id);
|
|
31
|
+
registerOperation({ identifier: id, method: resolvedMethod, level });
|
|
32
|
+
return (parent, args, context, info) => {
|
|
33
|
+
if (level === "public") return resolver(parent, args, context, info);
|
|
34
|
+
if (!context.argosUser) throw new Error("Unauthorized: Bearer token required.");
|
|
35
|
+
if (!client.checkPermission(context.argosUser, id, resolvedMethod)) {
|
|
36
|
+
throw new Error("Forbidden: Insufficient permissions.");
|
|
37
|
+
}
|
|
38
|
+
return resolver(parent, args, context, info);
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export { argosPlugin, createGuard };
|
|
44
|
+
//# sourceMappingURL=apollo.js.map
|
|
45
|
+
//# sourceMappingURL=apollo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/apollo.ts"],"names":[],"mappings":";;;;AAiBO,SAAS,YAAY,MAAA,EAAuD;AACjF,EAAA,OAAO;AAAA,IACL,MAAM,eAAA,GAAkB;AACtB,MAAA,OAAO;AAAA,QACL,MAAM,oBAAoB,GAAA,EAA0C;AAClE,UAAA,MAAM,WAAY,GAAA,CAAI,OAAA,CAAQ,MAAM,OAAA,CAAQ,GAAA,CAAI,eAAe,CAAA,IAAK,EAAA;AACpE,UAAA,MAAM,MAAA,GAAc,GAAA,CAAI,YAAA,EAAsB,gBAAA,EAAkB,aAAA,IAAwC,EAAA;AACxG,UAAA,MAAM,OAAY,QAAA,IAAY,MAAA;AAE9B,UAAA,MAAM,CAAC,MAAA,EAAQ,KAAK,CAAA,GAAI,IAAA,CAAK,MAAM,GAAG,CAAA;AACtC,UAAA,IAAI,MAAA,EAAQ,WAAA,EAAY,KAAM,QAAA,IAAY,KAAA,EAAO;AAC/C,YAAA,IAAI;AACF,cAAA,GAAA,CAAI,YAAA,CAAa,SAAA,GAAY,MAAM,MAAA,CAAO,cAAc,KAAK,CAAA;AAAA,YAC/D,SAAS,GAAA,EAAK;AACZ,cAAA,IAAI,eAAe,eAAA,EAAiB;AAClC,gBAAA,GAAA,CAAI,aAAa,SAAA,GAAY,MAAA;AAAA,cAC/B;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAAA,OACF;AAAA,IACF;AAAA,GACF;AACF;AAmBO,SAAS,YAAY,MAAA,EAAqB;AAC/C,EAAA,OAAO,SAAS,KAAA,CACd,EAAA,EACA,KAAA,EACA,UACA,MAAA,EAC6E;AAC7E,IAAA,MAAM,cAAA,GAAiB,MAAA,IAAU,YAAA,CAAa,EAAE,CAAA;AAChD,IAAA,iBAAA,CAAkB,EAAE,UAAA,EAAY,EAAA,EAAI,MAAA,EAAQ,cAAA,EAAgB,OAAO,CAAA;AAEnE,IAAA,OAAO,CAAC,MAAA,EAAQ,IAAA,EAAM,OAAA,EAAS,IAAA,KAAS;AACtC,MAAA,IAAI,UAAU,QAAA,EAAU,OAAO,SAAS,MAAA,EAAQ,IAAA,EAAM,SAAS,IAAI,CAAA;AACnE,MAAA,IAAI,CAAC,OAAA,CAAQ,SAAA,EAAW,MAAM,IAAI,MAAM,sCAAsC,CAAA;AAC9E,MAAA,IAAI,CAAC,MAAA,CAAO,eAAA,CAAgB,QAAQ,SAAA,EAAW,EAAA,EAAI,cAAc,CAAA,EAAG;AAClE,QAAA,MAAM,IAAI,MAAM,sCAAsC,CAAA;AAAA,MACxD;AACA,MAAA,OAAO,QAAA,CAAS,MAAA,EAAQ,IAAA,EAAM,OAAA,EAAS,IAAI,CAAA;AAAA,IAC7C,CAAA;AAAA,EACF,CAAA;AACF","file":"apollo.js","sourcesContent":["import type { ApolloServerPlugin, BaseContext, GraphQLRequestContext } from '@apollo/server';\nimport { ArgosClient } from '../client.js';\nimport { ArgosTokenError } from '../exceptions.js';\nimport type { TokenClaims } from '../models.js';\nimport { detectMethod, registerOperation } from '../registry.js';\n\nexport interface ArgosContext extends BaseContext {\n argosUser?: TokenClaims;\n}\n\n/**\n * Apollo Server plugin for argos.\n *\n * Validates the Bearer token on every request and stores the claims in\n * `context.argosUser`. Use `createGuard(client)` to enforce per-resolver\n * permissions.\n */\nexport function argosPlugin(client: ArgosClient): ApolloServerPlugin<ArgosContext> {\n return {\n async requestDidStart() {\n return {\n async didResolveOperation(ctx: GraphQLRequestContext<ArgosContext>) {\n const httpAuth = ctx.request.http?.headers.get('authorization') ?? '';\n const wsAuth = ((ctx.contextValue as any)?.connectionParams?.Authorization as string | undefined) ?? '';\n const auth = httpAuth || wsAuth;\n\n const [scheme, token] = auth.split(' ');\n if (scheme?.toLowerCase() === 'bearer' && token) {\n try {\n ctx.contextValue.argosUser = await client.validateToken(token);\n } catch (err) {\n if (err instanceof ArgosTokenError) {\n ctx.contextValue.argosUser = undefined;\n }\n }\n }\n },\n };\n },\n };\n}\n\n/**\n * Factory that returns a per-resolver guard bound to a `ArgosClient`.\n *\n * @example\n * ```ts\n * const guard = createGuard(client);\n *\n * const resolvers = {\n * Query: {\n * tasks: guard('tasks:list', 'private', async (_parent, _args, context) => {\n * const user = context.argosUser;\n * return db.tasks.findAll();\n * }),\n * },\n * };\n * ```\n */\nexport function createGuard(client: ArgosClient) {\n return function guard<TParent, TArgs, TContext extends ArgosContext, TReturn>(\n id: string,\n level: string,\n resolver: (parent: TParent, args: TArgs, context: TContext, info: unknown) => TReturn,\n method?: string,\n ): (parent: TParent, args: TArgs, context: TContext, info: unknown) => TReturn {\n const resolvedMethod = method ?? detectMethod(id);\n registerOperation({ identifier: id, method: resolvedMethod, level });\n\n return (parent, args, context, info) => {\n if (level === 'public') return resolver(parent, args, context, info);\n if (!context.argosUser) throw new Error('Unauthorized: Bearer token required.');\n if (!client.checkPermission(context.argosUser, id, resolvedMethod)) {\n throw new Error('Forbidden: Insufficient permissions.');\n }\n return resolver(parent, args, context, info);\n };\n };\n}"]}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/registry.ts
|
|
4
|
+
var _registry = [];
|
|
5
|
+
function detectMethod(id) {
|
|
6
|
+
const last = (id.split(":").pop() ?? "").toLowerCase();
|
|
7
|
+
if (/^(get|list|fetch|read)/.test(last)) return "read";
|
|
8
|
+
if (/^(delete|remove|destroy)/.test(last)) return "delete";
|
|
9
|
+
if (/(stream|subscribe|watch|listen)/.test(last)) return "stream";
|
|
10
|
+
return "write";
|
|
11
|
+
}
|
|
12
|
+
function registerOperation(op) {
|
|
13
|
+
const exists = _registry.some(
|
|
14
|
+
(o) => o.identifier === op.identifier && o.method === op.method
|
|
15
|
+
);
|
|
16
|
+
if (!exists) _registry.push(op);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// src/adapters/express.ts
|
|
20
|
+
function argos(client) {
|
|
21
|
+
const middleware = async (req, _res, next) => {
|
|
22
|
+
const auth = req.headers.authorization ?? "";
|
|
23
|
+
const [scheme, token] = auth.split(" ");
|
|
24
|
+
if (scheme?.toLowerCase() === "bearer" && token) {
|
|
25
|
+
try {
|
|
26
|
+
req.argosUser = await client.validateToken(token);
|
|
27
|
+
} catch {
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
next();
|
|
31
|
+
};
|
|
32
|
+
const guard = (id, level, method) => {
|
|
33
|
+
const resolvedMethod = method ?? detectMethod(id);
|
|
34
|
+
registerOperation({ identifier: id, method: resolvedMethod, level });
|
|
35
|
+
return (req, res, next) => {
|
|
36
|
+
if (level === "public") {
|
|
37
|
+
next();
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (!req.argosUser) {
|
|
41
|
+
res.status(401).json({ error: "unauthorized", detail: "Bearer token required." });
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const allowed = client.checkPermission(req.argosUser, id, resolvedMethod);
|
|
45
|
+
if (!allowed) {
|
|
46
|
+
res.status(403).json({ error: "forbidden", detail: "Insufficient permissions." });
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
next();
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
return { middleware, guard };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
exports.argos = argos;
|
|
56
|
+
//# sourceMappingURL=express.cjs.map
|
|
57
|
+
//# sourceMappingURL=express.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/registry.ts","../../src/adapters/express.ts"],"names":[],"mappings":";;;AAEA,IAAM,YAAmC,EAAC;AAKnC,SAAS,aAAa,EAAA,EAAoB;AAC/C,EAAA,MAAM,IAAA,GAAA,CAAQ,GAAG,KAAA,CAAM,GAAG,EAAE,GAAA,EAAI,IAAK,IAAI,WAAA,EAAY;AACrD,EAAA,IAAI,wBAAA,CAAyB,IAAA,CAAK,IAAI,CAAA,EAAS,OAAO,MAAA;AACtD,EAAA,IAAI,0BAAA,CAA2B,IAAA,CAAK,IAAI,CAAA,EAAQ,OAAO,QAAA;AACvD,EAAA,IAAI,iCAAA,CAAkC,IAAA,CAAK,IAAI,CAAA,EAAG,OAAO,QAAA;AACzD,EAAA,OAAO,OAAA;AACT;AAGO,SAAS,kBAAkB,EAAA,EAA+B;AAC/D,EAAA,MAAM,SAAS,SAAA,CAAU,IAAA;AAAA,IACvB,OAAK,CAAA,CAAE,UAAA,KAAe,GAAG,UAAA,IAAc,CAAA,CAAE,WAAW,EAAA,CAAG;AAAA,GACzD;AACA,EAAA,IAAI,CAAC,MAAA,EAAQ,SAAA,CAAU,IAAA,CAAK,EAAE,CAAA;AAChC;;;ACCO,SAAS,MAAM,MAAA,EAAsC;AAE1D,EAAA,MAAM,UAAA,GAAa,OAAO,GAAA,EAAc,IAAA,EAAgB,IAAA,KAAsC;AAC5F,IAAA,MAAM,IAAA,GAAO,GAAA,CAAI,OAAA,CAAQ,aAAA,IAAiB,EAAA;AAC1C,IAAA,MAAM,CAAC,MAAA,EAAQ,KAAK,CAAA,GAAI,IAAA,CAAK,MAAM,GAAG,CAAA;AACtC,IAAA,IAAI,MAAA,EAAQ,WAAA,EAAY,KAAM,QAAA,IAAY,KAAA,EAAO;AAC/C,MAAA,IAAI;AACF,QAAA,GAAA,CAAI,SAAA,GAAY,MAAM,MAAA,CAAO,aAAA,CAAc,KAAK,CAAA;AAAA,MAClD,CAAA,CAAA,MAAQ;AAAA,MAER;AAAA,IACF;AACA,IAAA,IAAA,EAAK;AAAA,EACP,CAAA;AAEA,EAAA,MAAM,KAAA,GAAQ,CAAC,EAAA,EAAY,KAAA,EAAe,MAAA,KAAoB;AAC5D,IAAA,MAAM,cAAA,GAAiB,MAAA,IAAU,YAAA,CAAa,EAAE,CAAA;AAChD,IAAA,iBAAA,CAAkB,EAAE,UAAA,EAAY,EAAA,EAAI,MAAA,EAAQ,cAAA,EAAgB,OAAO,CAAA;AAEnE,IAAA,OAAO,CAAC,GAAA,EAAc,GAAA,EAAe,IAAA,KAA6B;AAChE,MAAA,IAAI,UAAU,QAAA,EAAU;AAAE,QAAA,IAAA,EAAK;AAAG,QAAA;AAAA,MAAQ;AAE1C,MAAA,IAAI,CAAC,IAAI,SAAA,EAAW;AAClB,QAAA,GAAA,CAAI,MAAA,CAAO,GAAG,CAAA,CAAE,IAAA,CAAK,EAAE,KAAA,EAAO,cAAA,EAAgB,MAAA,EAAQ,wBAAA,EAA0B,CAAA;AAChF,QAAA;AAAA,MACF;AAEA,MAAA,MAAM,UAAU,MAAA,CAAO,eAAA,CAAgB,GAAA,CAAI,SAAA,EAAW,IAAI,cAAc,CAAA;AACxE,MAAA,IAAI,CAAC,OAAA,EAAS;AACZ,QAAA,GAAA,CAAI,MAAA,CAAO,GAAG,CAAA,CAAE,IAAA,CAAK,EAAE,KAAA,EAAO,WAAA,EAAa,MAAA,EAAQ,2BAAA,EAA6B,CAAA;AAChF,QAAA;AAAA,MACF;AAEA,MAAA,IAAA,EAAK;AAAA,IACP,CAAA;AAAA,EACF,CAAA;AAEA,EAAA,OAAO,EAAE,YAAY,KAAA,EAAM;AAC7B","file":"express.cjs","sourcesContent":["import type { OperationDescriptor } from './models.js';\n\nconst _registry: OperationDescriptor[] = [];\n\n/** Infer operation method from the last segment of the operation id.\n * e.g. \"tasks:list\" → \"read\", \"tasks:create\" → \"write\", \"tasks:delete\" → \"delete\"\n */\nexport function detectMethod(id: string): string {\n const last = (id.split(':').pop() ?? '').toLowerCase();\n if (/^(get|list|fetch|read)/.test(last)) return 'read';\n if (/^(delete|remove|destroy)/.test(last)) return 'delete';\n if (/(stream|subscribe|watch|listen)/.test(last)) return 'stream';\n return 'write';\n}\n\n/** Register an operation in the global registry (used by startup to sync). */\nexport function registerOperation(op: OperationDescriptor): void {\n const exists = _registry.some(\n o => o.identifier === op.identifier && o.method === op.method,\n );\n if (!exists) _registry.push(op);\n}\n\nexport function getRegistry(): OperationDescriptor[] {\n return [..._registry];\n}\n\nexport function clearRegistry(): void {\n _registry.length = 0;\n}","import type { NextFunction, Request, Response } from 'express';\nimport { ArgosClient } from '../client.js';\nimport { ArgosTokenError } from '../exceptions.js';\nimport type { TokenClaims } from '../models.js';\nimport { detectMethod, registerOperation } from '../registry.js';\n\ndeclare global {\n namespace Express {\n interface Request {\n argosUser?: TokenClaims;\n }\n }\n}\n\nexport interface ArgosMiddleware {\n /** Global middleware — validates the Bearer token and injects `req.argosUser`. */\n middleware: (req: Request, res: Response, next: NextFunction) => Promise<void>;\n /** Per-route guard — registers the operation and enforces permission at request time. */\n guard: (id: string, level: string, method?: string) =>\n (req: Request, res: Response, next: NextFunction) => void;\n}\n\nexport function argos(client: ArgosClient): ArgosMiddleware {\n\n const middleware = async (req: Request, _res: Response, next: NextFunction): Promise<void> => {\n const auth = req.headers.authorization ?? '';\n const [scheme, token] = auth.split(' ');\n if (scheme?.toLowerCase() === 'bearer' && token) {\n try {\n req.argosUser = await client.validateToken(token);\n } catch {\n // invalid token — argosUser stays undefined; guard handles the 401\n }\n }\n next();\n };\n\n const guard = (id: string, level: string, method?: string) => {\n const resolvedMethod = method ?? detectMethod(id);\n registerOperation({ identifier: id, method: resolvedMethod, level });\n\n return (req: Request, res: Response, next: NextFunction): void => {\n if (level === 'public') { next(); return; }\n\n if (!req.argosUser) {\n res.status(401).json({ error: 'unauthorized', detail: 'Bearer token required.' });\n return;\n }\n\n const allowed = client.checkPermission(req.argosUser, id, resolvedMethod);\n if (!allowed) {\n res.status(403).json({ error: 'forbidden', detail: 'Insufficient permissions.' });\n return;\n }\n\n next();\n };\n };\n\n return { middleware, guard };\n}"]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Request, Response, NextFunction } from 'express';
|
|
2
|
+
import { T as TokenClaims, A as ArgosClient } from '../client-Cgm1csuM.cjs';
|
|
3
|
+
|
|
4
|
+
declare global {
|
|
5
|
+
namespace Express {
|
|
6
|
+
interface Request {
|
|
7
|
+
argosUser?: TokenClaims;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
interface ArgosMiddleware {
|
|
12
|
+
/** Global middleware — validates the Bearer token and injects `req.argosUser`. */
|
|
13
|
+
middleware: (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
14
|
+
/** Per-route guard — registers the operation and enforces permission at request time. */
|
|
15
|
+
guard: (id: string, level: string, method?: string) => (req: Request, res: Response, next: NextFunction) => void;
|
|
16
|
+
}
|
|
17
|
+
declare function argos(client: ArgosClient): ArgosMiddleware;
|
|
18
|
+
|
|
19
|
+
export { type ArgosMiddleware, argos };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Request, Response, NextFunction } from 'express';
|
|
2
|
+
import { T as TokenClaims, A as ArgosClient } from '../client-Cgm1csuM.js';
|
|
3
|
+
|
|
4
|
+
declare global {
|
|
5
|
+
namespace Express {
|
|
6
|
+
interface Request {
|
|
7
|
+
argosUser?: TokenClaims;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
interface ArgosMiddleware {
|
|
12
|
+
/** Global middleware — validates the Bearer token and injects `req.argosUser`. */
|
|
13
|
+
middleware: (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
14
|
+
/** Per-route guard — registers the operation and enforces permission at request time. */
|
|
15
|
+
guard: (id: string, level: string, method?: string) => (req: Request, res: Response, next: NextFunction) => void;
|
|
16
|
+
}
|
|
17
|
+
declare function argos(client: ArgosClient): ArgosMiddleware;
|
|
18
|
+
|
|
19
|
+
export { type ArgosMiddleware, argos };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { detectMethod, registerOperation } from '../chunk-HV6X5RJY.js';
|
|
2
|
+
|
|
3
|
+
// src/adapters/express.ts
|
|
4
|
+
function argos(client) {
|
|
5
|
+
const middleware = async (req, _res, next) => {
|
|
6
|
+
const auth = req.headers.authorization ?? "";
|
|
7
|
+
const [scheme, token] = auth.split(" ");
|
|
8
|
+
if (scheme?.toLowerCase() === "bearer" && token) {
|
|
9
|
+
try {
|
|
10
|
+
req.argosUser = await client.validateToken(token);
|
|
11
|
+
} catch {
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
next();
|
|
15
|
+
};
|
|
16
|
+
const guard = (id, level, method) => {
|
|
17
|
+
const resolvedMethod = method ?? detectMethod(id);
|
|
18
|
+
registerOperation({ identifier: id, method: resolvedMethod, level });
|
|
19
|
+
return (req, res, next) => {
|
|
20
|
+
if (level === "public") {
|
|
21
|
+
next();
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (!req.argosUser) {
|
|
25
|
+
res.status(401).json({ error: "unauthorized", detail: "Bearer token required." });
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const allowed = client.checkPermission(req.argosUser, id, resolvedMethod);
|
|
29
|
+
if (!allowed) {
|
|
30
|
+
res.status(403).json({ error: "forbidden", detail: "Insufficient permissions." });
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
next();
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
return { middleware, guard };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export { argos };
|
|
40
|
+
//# sourceMappingURL=express.js.map
|
|
41
|
+
//# sourceMappingURL=express.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/express.ts"],"names":[],"mappings":";;;AAsBO,SAAS,MAAM,MAAA,EAAsC;AAE1D,EAAA,MAAM,UAAA,GAAa,OAAO,GAAA,EAAc,IAAA,EAAgB,IAAA,KAAsC;AAC5F,IAAA,MAAM,IAAA,GAAO,GAAA,CAAI,OAAA,CAAQ,aAAA,IAAiB,EAAA;AAC1C,IAAA,MAAM,CAAC,MAAA,EAAQ,KAAK,CAAA,GAAI,IAAA,CAAK,MAAM,GAAG,CAAA;AACtC,IAAA,IAAI,MAAA,EAAQ,WAAA,EAAY,KAAM,QAAA,IAAY,KAAA,EAAO;AAC/C,MAAA,IAAI;AACF,QAAA,GAAA,CAAI,SAAA,GAAY,MAAM,MAAA,CAAO,aAAA,CAAc,KAAK,CAAA;AAAA,MAClD,CAAA,CAAA,MAAQ;AAAA,MAER;AAAA,IACF;AACA,IAAA,IAAA,EAAK;AAAA,EACP,CAAA;AAEA,EAAA,MAAM,KAAA,GAAQ,CAAC,EAAA,EAAY,KAAA,EAAe,MAAA,KAAoB;AAC5D,IAAA,MAAM,cAAA,GAAiB,MAAA,IAAU,YAAA,CAAa,EAAE,CAAA;AAChD,IAAA,iBAAA,CAAkB,EAAE,UAAA,EAAY,EAAA,EAAI,MAAA,EAAQ,cAAA,EAAgB,OAAO,CAAA;AAEnE,IAAA,OAAO,CAAC,GAAA,EAAc,GAAA,EAAe,IAAA,KAA6B;AAChE,MAAA,IAAI,UAAU,QAAA,EAAU;AAAE,QAAA,IAAA,EAAK;AAAG,QAAA;AAAA,MAAQ;AAE1C,MAAA,IAAI,CAAC,IAAI,SAAA,EAAW;AAClB,QAAA,GAAA,CAAI,MAAA,CAAO,GAAG,CAAA,CAAE,IAAA,CAAK,EAAE,KAAA,EAAO,cAAA,EAAgB,MAAA,EAAQ,wBAAA,EAA0B,CAAA;AAChF,QAAA;AAAA,MACF;AAEA,MAAA,MAAM,UAAU,MAAA,CAAO,eAAA,CAAgB,GAAA,CAAI,SAAA,EAAW,IAAI,cAAc,CAAA;AACxE,MAAA,IAAI,CAAC,OAAA,EAAS;AACZ,QAAA,GAAA,CAAI,MAAA,CAAO,GAAG,CAAA,CAAE,IAAA,CAAK,EAAE,KAAA,EAAO,WAAA,EAAa,MAAA,EAAQ,2BAAA,EAA6B,CAAA;AAChF,QAAA;AAAA,MACF;AAEA,MAAA,IAAA,EAAK;AAAA,IACP,CAAA;AAAA,EACF,CAAA;AAEA,EAAA,OAAO,EAAE,YAAY,KAAA,EAAM;AAC7B","file":"express.js","sourcesContent":["import type { NextFunction, Request, Response } from 'express';\nimport { ArgosClient } from '../client.js';\nimport { ArgosTokenError } from '../exceptions.js';\nimport type { TokenClaims } from '../models.js';\nimport { detectMethod, registerOperation } from '../registry.js';\n\ndeclare global {\n namespace Express {\n interface Request {\n argosUser?: TokenClaims;\n }\n }\n}\n\nexport interface ArgosMiddleware {\n /** Global middleware — validates the Bearer token and injects `req.argosUser`. */\n middleware: (req: Request, res: Response, next: NextFunction) => Promise<void>;\n /** Per-route guard — registers the operation and enforces permission at request time. */\n guard: (id: string, level: string, method?: string) =>\n (req: Request, res: Response, next: NextFunction) => void;\n}\n\nexport function argos(client: ArgosClient): ArgosMiddleware {\n\n const middleware = async (req: Request, _res: Response, next: NextFunction): Promise<void> => {\n const auth = req.headers.authorization ?? '';\n const [scheme, token] = auth.split(' ');\n if (scheme?.toLowerCase() === 'bearer' && token) {\n try {\n req.argosUser = await client.validateToken(token);\n } catch {\n // invalid token — argosUser stays undefined; guard handles the 401\n }\n }\n next();\n };\n\n const guard = (id: string, level: string, method?: string) => {\n const resolvedMethod = method ?? detectMethod(id);\n registerOperation({ identifier: id, method: resolvedMethod, level });\n\n return (req: Request, res: Response, next: NextFunction): void => {\n if (level === 'public') { next(); return; }\n\n if (!req.argosUser) {\n res.status(401).json({ error: 'unauthorized', detail: 'Bearer token required.' });\n return;\n }\n\n const allowed = client.checkPermission(req.argosUser, id, resolvedMethod);\n if (!allowed) {\n res.status(403).json({ error: 'forbidden', detail: 'Insufficient permissions.' });\n return;\n }\n\n next();\n };\n };\n\n return { middleware, guard };\n}"]}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var fp = require('fastify-plugin');
|
|
4
|
+
|
|
5
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
6
|
+
|
|
7
|
+
var fp__default = /*#__PURE__*/_interopDefault(fp);
|
|
8
|
+
|
|
9
|
+
// src/adapters/fastify.ts
|
|
10
|
+
|
|
11
|
+
// src/registry.ts
|
|
12
|
+
var _registry = [];
|
|
13
|
+
function detectMethod(id) {
|
|
14
|
+
const last = (id.split(":").pop() ?? "").toLowerCase();
|
|
15
|
+
if (/^(get|list|fetch|read)/.test(last)) return "read";
|
|
16
|
+
if (/^(delete|remove|destroy)/.test(last)) return "delete";
|
|
17
|
+
if (/(stream|subscribe|watch|listen)/.test(last)) return "stream";
|
|
18
|
+
return "write";
|
|
19
|
+
}
|
|
20
|
+
function registerOperation(op) {
|
|
21
|
+
const exists = _registry.some(
|
|
22
|
+
(o) => o.identifier === op.identifier && o.method === op.method
|
|
23
|
+
);
|
|
24
|
+
if (!exists) _registry.push(op);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// src/adapters/fastify.ts
|
|
28
|
+
var argosPlugin = async (fastify, { client }) => {
|
|
29
|
+
fastify.decorateRequest("argosUser", void 0);
|
|
30
|
+
fastify.addHook("onRequest", async (request) => {
|
|
31
|
+
const auth = request.headers.authorization ?? "";
|
|
32
|
+
const [scheme, token] = auth.split(" ");
|
|
33
|
+
if (scheme?.toLowerCase() === "bearer" && token) {
|
|
34
|
+
try {
|
|
35
|
+
request.argosUser = await client.validateToken(token);
|
|
36
|
+
} catch {
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
fastify.decorate(
|
|
41
|
+
"argosGuard",
|
|
42
|
+
(id, level, method) => {
|
|
43
|
+
const resolvedMethod = method ?? detectMethod(id);
|
|
44
|
+
registerOperation({ identifier: id, method: resolvedMethod, level });
|
|
45
|
+
return async (request, reply) => {
|
|
46
|
+
if (level === "public") return;
|
|
47
|
+
if (!request.argosUser) {
|
|
48
|
+
await reply.status(401).send({ error: "unauthorized", detail: "Bearer token required." });
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const allowed = client.checkPermission(request.argosUser, id, resolvedMethod);
|
|
52
|
+
if (!allowed) {
|
|
53
|
+
await reply.status(403).send({ error: "forbidden", detail: "Insufficient permissions." });
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
);
|
|
58
|
+
};
|
|
59
|
+
var argosFastifyPlugin = fp__default.default(argosPlugin, {
|
|
60
|
+
name: "argos",
|
|
61
|
+
fastify: ">=4"
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
exports.argosFastifyPlugin = argosFastifyPlugin;
|
|
65
|
+
//# sourceMappingURL=fastify.cjs.map
|
|
66
|
+
//# sourceMappingURL=fastify.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/registry.ts","../../src/adapters/fastify.ts"],"names":["fp"],"mappings":";;;;;;;;;;;AAEA,IAAM,YAAmC,EAAC;AAKnC,SAAS,aAAa,EAAA,EAAoB;AAC/C,EAAA,MAAM,IAAA,GAAA,CAAQ,GAAG,KAAA,CAAM,GAAG,EAAE,GAAA,EAAI,IAAK,IAAI,WAAA,EAAY;AACrD,EAAA,IAAI,wBAAA,CAAyB,IAAA,CAAK,IAAI,CAAA,EAAS,OAAO,MAAA;AACtD,EAAA,IAAI,0BAAA,CAA2B,IAAA,CAAK,IAAI,CAAA,EAAQ,OAAO,QAAA;AACvD,EAAA,IAAI,iCAAA,CAAkC,IAAA,CAAK,IAAI,CAAA,EAAG,OAAO,QAAA;AACzD,EAAA,OAAO,OAAA;AACT;AAGO,SAAS,kBAAkB,EAAA,EAA+B;AAC/D,EAAA,MAAM,SAAS,SAAA,CAAU,IAAA;AAAA,IACvB,OAAK,CAAA,CAAE,UAAA,KAAe,GAAG,UAAA,IAAc,CAAA,CAAE,WAAW,EAAA,CAAG;AAAA,GACzD;AACA,EAAA,IAAI,CAAC,MAAA,EAAQ,SAAA,CAAU,IAAA,CAAK,EAAE,CAAA;AAChC;;;ACLA,IAAM,WAAA,GAAsD,OAC1D,OAAA,EACA,EAAE,QAAO,KACN;AAEH,EAAA,OAAA,CAAQ,eAAA,CAAyC,aAAa,MAAS,CAAA;AAGvE,EAAA,OAAA,CAAQ,OAAA,CAAQ,WAAA,EAAa,OAAO,OAAA,KAA4B;AAC9D,IAAA,MAAM,IAAA,GAAO,OAAA,CAAQ,OAAA,CAAQ,aAAA,IAAiB,EAAA;AAC9C,IAAA,MAAM,CAAC,MAAA,EAAQ,KAAK,CAAA,GAAI,IAAA,CAAK,MAAM,GAAG,CAAA;AACtC,IAAA,IAAI,MAAA,EAAQ,WAAA,EAAY,KAAM,QAAA,IAAY,KAAA,EAAO;AAC/C,MAAA,IAAI;AACF,QAAA,OAAA,CAAQ,SAAA,GAAY,MAAM,MAAA,CAAO,aAAA,CAAc,KAAK,CAAA;AAAA,MACtD,CAAA,CAAA,MAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF,CAAC,CAAA;AAGD,EAAA,OAAA,CAAQ,QAAA;AAAA,IACN,YAAA;AAAA,IACA,CAAC,EAAA,EAAY,KAAA,EAAe,MAAA,KAAoB;AAC9C,MAAA,MAAM,cAAA,GAAiB,MAAA,IAAU,YAAA,CAAa,EAAE,CAAA;AAChD,MAAA,iBAAA,CAAkB,EAAE,UAAA,EAAY,EAAA,EAAI,MAAA,EAAQ,cAAA,EAAgB,OAAO,CAAA;AAEnE,MAAA,OAAO,OAAO,SAAyB,KAAA,KAAuC;AAC5E,QAAA,IAAI,UAAU,QAAA,EAAU;AAExB,QAAA,IAAI,CAAC,QAAQ,SAAA,EAAW;AACtB,UAAA,MAAM,KAAA,CAAM,MAAA,CAAO,GAAG,CAAA,CAAE,IAAA,CAAK,EAAE,KAAA,EAAO,cAAA,EAAgB,MAAA,EAAQ,wBAAA,EAA0B,CAAA;AACxF,UAAA;AAAA,QACF;AAEA,QAAA,MAAM,UAAU,MAAA,CAAO,eAAA,CAAgB,OAAA,CAAQ,SAAA,EAAW,IAAI,cAAc,CAAA;AAC5E,QAAA,IAAI,CAAC,OAAA,EAAS;AACZ,UAAA,MAAM,KAAA,CAAM,MAAA,CAAO,GAAG,CAAA,CAAE,IAAA,CAAK,EAAE,KAAA,EAAO,WAAA,EAAa,MAAA,EAAQ,2BAAA,EAA6B,CAAA;AAAA,QAC1F;AAAA,MACF,CAAA;AAAA,IACF;AAAA,GACF;AACF,CAAA;AAEO,IAAM,kBAAA,GAAqBA,oBAAG,WAAA,EAAa;AAAA,EAChD,IAAA,EAAM,OAAA;AAAA,EACN,OAAA,EAAS;AACX,CAAC","file":"fastify.cjs","sourcesContent":["import type { OperationDescriptor } from './models.js';\n\nconst _registry: OperationDescriptor[] = [];\n\n/** Infer operation method from the last segment of the operation id.\n * e.g. \"tasks:list\" → \"read\", \"tasks:create\" → \"write\", \"tasks:delete\" → \"delete\"\n */\nexport function detectMethod(id: string): string {\n const last = (id.split(':').pop() ?? '').toLowerCase();\n if (/^(get|list|fetch|read)/.test(last)) return 'read';\n if (/^(delete|remove|destroy)/.test(last)) return 'delete';\n if (/(stream|subscribe|watch|listen)/.test(last)) return 'stream';\n return 'write';\n}\n\n/** Register an operation in the global registry (used by startup to sync). */\nexport function registerOperation(op: OperationDescriptor): void {\n const exists = _registry.some(\n o => o.identifier === op.identifier && o.method === op.method,\n );\n if (!exists) _registry.push(op);\n}\n\nexport function getRegistry(): OperationDescriptor[] {\n return [..._registry];\n}\n\nexport function clearRegistry(): void {\n _registry.length = 0;\n}","import type { FastifyInstance, FastifyPluginAsync, FastifyReply, FastifyRequest } from 'fastify';\nimport fp from 'fastify-plugin';\nimport { ArgosClient } from '../client.js';\nimport type { TokenClaims } from '../models.js';\nimport { detectMethod, registerOperation } from '../registry.js';\n\ndeclare module 'fastify' {\n interface FastifyRequest {\n argosUser?: TokenClaims;\n }\n}\n\nexport interface ArgosPluginOptions {\n client: ArgosClient;\n}\n\nconst argosPlugin: FastifyPluginAsync<ArgosPluginOptions> = async (\n fastify: FastifyInstance,\n { client }: ArgosPluginOptions,\n) => {\n // Decorate request with argosUser\n fastify.decorateRequest<TokenClaims | undefined>('argosUser', undefined);\n\n // Global hook — validates Bearer token on every request\n fastify.addHook('onRequest', async (request: FastifyRequest) => {\n const auth = request.headers.authorization ?? '';\n const [scheme, token] = auth.split(' ');\n if (scheme?.toLowerCase() === 'bearer' && token) {\n try {\n request.argosUser = await client.validateToken(token);\n } catch {\n // invalid token — argosUser stays null; guard handles the 401\n }\n }\n });\n\n // Decorate fastify with a guard factory\n fastify.decorate(\n 'argosGuard',\n (id: string, level: string, method?: string) => {\n const resolvedMethod = method ?? detectMethod(id);\n registerOperation({ identifier: id, method: resolvedMethod, level });\n\n return async (request: FastifyRequest, reply: FastifyReply): Promise<void> => {\n if (level === 'public') return;\n\n if (!request.argosUser) {\n await reply.status(401).send({ error: 'unauthorized', detail: 'Bearer token required.' });\n return;\n }\n\n const allowed = client.checkPermission(request.argosUser, id, resolvedMethod);\n if (!allowed) {\n await reply.status(403).send({ error: 'forbidden', detail: 'Insufficient permissions.' });\n }\n };\n },\n );\n};\n\nexport const argosFastifyPlugin = fp(argosPlugin, {\n name: 'argos',\n fastify: '>=4',\n});\n\ndeclare module 'fastify' {\n interface FastifyInstance {\n argosGuard: (\n id: string,\n level: string,\n method?: string,\n ) => (req: FastifyRequest, reply: FastifyReply) => Promise<void>;\n }\n}"]}
|