@cleverclouds/auth-client 0.0.0-stage → 1.0.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/AGENTS.md +379 -0
- package/README.md +450 -2
- package/dist/client.d.ts +61 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +226 -0
- package/dist/client.js.map +1 -0
- package/dist/crypto.d.ts +12 -0
- package/dist/crypto.d.ts.map +1 -0
- package/dist/crypto.js +66 -0
- package/dist/crypto.js.map +1 -0
- package/dist/errors.d.ts +22 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +49 -0
- package/dist/errors.js.map +1 -0
- package/dist/express/index.d.ts +18 -0
- package/dist/express/index.d.ts.map +1 -0
- package/dist/express/index.js +296 -0
- package/dist/express/index.js.map +1 -0
- package/dist/express/types.d.ts +70 -0
- package/dist/express/types.d.ts.map +1 -0
- package/dist/express/types.js +3 -0
- package/dist/express/types.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/nextjs/index.d.ts +33 -0
- package/dist/nextjs/index.d.ts.map +1 -0
- package/dist/nextjs/index.js +178 -0
- package/dist/nextjs/index.js.map +1 -0
- package/dist/nextjs/types.d.ts +51 -0
- package/dist/nextjs/types.d.ts.map +1 -0
- package/dist/nextjs/types.js +3 -0
- package/dist/nextjs/types.js.map +1 -0
- package/dist/permissions.d.ts +25 -0
- package/dist/permissions.d.ts.map +1 -0
- package/dist/permissions.js +62 -0
- package/dist/permissions.js.map +1 -0
- package/dist/types.d.ts +53 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/package.json +86 -4
package/README.md
CHANGED
|
@@ -1,3 +1,451 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @cleverclouds/auth-client
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@cleverclouds/auth-client)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
[](https://www.typescriptlang.org/)
|
|
6
|
+
[](#)
|
|
7
|
+
|
|
8
|
+
> Lightweight, framework-agnostic client SDK for **CleverClouds Identity (OneAuth)** with native Express and Next.js App Router adapters, tamper-proof HMAC-SHA256 session cookies, and fine-grained RBAC evaluation.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Table of Contents
|
|
13
|
+
|
|
14
|
+
- [Features](#features)
|
|
15
|
+
- [Architecture & Mental Model](#architecture--mental-model)
|
|
16
|
+
- [Installation](#installation)
|
|
17
|
+
- [Configuration](#configuration)
|
|
18
|
+
- [Subpath Exports](#subpath-exports)
|
|
19
|
+
- [Express.js Integration](#expressjs-integration)
|
|
20
|
+
- [Next.js App Router Integration](#nextjs-app-router-integration)
|
|
21
|
+
- [Fastify / Raw Node.js Integration](#fastify--raw-nodejs-integration)
|
|
22
|
+
- [Frontend (SPA) Integration Pattern](#frontend-spa-integration-pattern)
|
|
23
|
+
- [Role-Based Access Control (RBAC)](#role-based-access-control-rbac)
|
|
24
|
+
- [Cryptographic Cookie Security](#cryptographic-cookie-security)
|
|
25
|
+
- [Error Handling](#error-handling)
|
|
26
|
+
- [AI Agents & LLMs Guide](#ai-agents--llms-guide)
|
|
27
|
+
- [License](#license)
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Features
|
|
32
|
+
|
|
33
|
+
- 🚀 **Zero external runtime dependencies** for core client (uses Node.js standard `fetch` & `crypto`).
|
|
34
|
+
- 🛡️ **Tamper-Proof Session Security**: Built-in HMAC-SHA256 cookie signing and constant-time verification (`crypto.timingSafeEqual`).
|
|
35
|
+
- ⚡ **Express Middleware Adapter**: Drop-in `requireAuth()`, `requirePermission()`, `requireRole()`, `handleCallback()`, and `handleLogout()`.
|
|
36
|
+
- 🌐 **Next.js App Router Adapter**: Server Component, Server Action, and Route Handler session extractors (`getServerSession()`, `protectRoute()`).
|
|
37
|
+
- 🎯 **Fine-Grained RBAC**: Exact permission matching, resource wildcards (`projects.*`), platform wildcards (`*.*`), and case-insensitive role checks.
|
|
38
|
+
- 🔒 **Defense-in-Depth Open Redirect Protection**: Built-in hostname validation and relative-path enforcement on callback redirections.
|
|
39
|
+
- 🤖 **Agent-Ready**: Includes a dedicated [AGENTS.md](AGENTS.md) specification packed directly in the distribution for AI coding assistants.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Architecture & Mental Model
|
|
44
|
+
|
|
45
|
+
```mermaid
|
|
46
|
+
sequenceDiagram
|
|
47
|
+
autonumber
|
|
48
|
+
actor User as Employee Browser
|
|
49
|
+
participant App as Downstream App (Express / Next.js)
|
|
50
|
+
participant Identity as CleverClouds Identity (OneAuth)
|
|
51
|
+
|
|
52
|
+
User->>App: GET /dashboard (unauthenticated)
|
|
53
|
+
App-->>User: 302 Redirect to Identity /login?app=projects&returnTo=...
|
|
54
|
+
User->>Identity: Authenticate (SSO / Credentials / MFA)
|
|
55
|
+
Identity->>Identity: Validate registered redirect URL & generate 60s ticket
|
|
56
|
+
Identity-->>User: 302 Redirect to /api/auth/callback?ticket=cc_ticket_...
|
|
57
|
+
User->>App: GET /api/auth/callback?ticket=cc_ticket_...
|
|
58
|
+
App->>Identity: POST /api/integration/token-exchange (X-Application-Key)
|
|
59
|
+
Identity-->>App: 200 OK { user, roles, permissions }
|
|
60
|
+
App->>App: Sign session payload (HMAC-SHA256) via sessionSecret
|
|
61
|
+
App-->>User: 302 Redirect to /dashboard + Set Signed Session Cookie
|
|
62
|
+
User->>App: GET /dashboard (with signed cookie)
|
|
63
|
+
App->>App: Constant-time HMAC verify & grant access
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Installation
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# Using npm:
|
|
72
|
+
npm install @cleverclouds/auth-client
|
|
73
|
+
|
|
74
|
+
# Using yarn:
|
|
75
|
+
yarn add @cleverclouds/auth-client
|
|
76
|
+
|
|
77
|
+
# Using pnpm:
|
|
78
|
+
pnpm add @cleverclouds/auth-client
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Peer Dependencies
|
|
82
|
+
If using the framework adapters:
|
|
83
|
+
- **Express**: `npm install express cookie-parser`
|
|
84
|
+
- **Next.js**: `npm install next` (v14 or v15)
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Configuration
|
|
89
|
+
|
|
90
|
+
Set the following environment variables in your downstream application:
|
|
91
|
+
|
|
92
|
+
```env
|
|
93
|
+
# URL of your CleverClouds Identity instance
|
|
94
|
+
CC_IDENTITY_URL=https://auth.cleverclouds.in
|
|
95
|
+
|
|
96
|
+
# Application slug registered in CleverClouds Identity
|
|
97
|
+
CC_APP_SLUG=projects
|
|
98
|
+
|
|
99
|
+
# Application API key issued from the Identity Admin Console
|
|
100
|
+
CC_APP_KEY=cc_app_projects_xxxxxxxxxxxxxxxx
|
|
101
|
+
|
|
102
|
+
# Registered callback URI
|
|
103
|
+
CC_REDIRECT_URI=https://projects.cleverclouds.in/api/auth/callback
|
|
104
|
+
|
|
105
|
+
# Strong secret for signing downstream session cookies (minimum 32 characters)
|
|
106
|
+
SESSION_SECRET=a_very_strong_random_secret_at_least_32_characters_long
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Initializing the Client Singleton
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
// src/lib/auth.ts
|
|
113
|
+
import { CleverCloudsAuthClient } from '@cleverclouds/auth-client';
|
|
114
|
+
|
|
115
|
+
export const auth = new CleverCloudsAuthClient({
|
|
116
|
+
identityBaseUrl: process.env.CC_IDENTITY_URL || 'http://localhost:3000',
|
|
117
|
+
applicationSlug: process.env.CC_APP_SLUG || 'projects',
|
|
118
|
+
applicationApiKey: process.env.CC_APP_KEY!,
|
|
119
|
+
redirectUri: process.env.CC_REDIRECT_URI || 'http://localhost:3001/api/auth/callback',
|
|
120
|
+
sessionSecret: process.env.SESSION_SECRET!, // Mandatory when using cookie sessions
|
|
121
|
+
});
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Subpath Exports
|
|
127
|
+
|
|
128
|
+
| Import Path | Description | Key Exports |
|
|
129
|
+
| :--- | :--- | :--- |
|
|
130
|
+
| `@cleverclouds/auth-client` | Core client, errors, and pure RBAC evaluators | `CleverCloudsAuthClient`, `hasPermission`, `hasRole`, `hasAnyPermission` |
|
|
131
|
+
| `@cleverclouds/auth-client/express` | Express.js middleware and route handlers | `createAuthMiddleware` |
|
|
132
|
+
| `@cleverclouds/auth-client/nextjs` | Next.js App Router helpers | `getServerSession`, `protectRoute` |
|
|
133
|
+
| `@cleverclouds/auth-client/cookies` | Low-level HMAC signer and verifier | `signCookie`, `verifyCookie` |
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Express.js Integration
|
|
138
|
+
|
|
139
|
+
### Full Server Setup
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
import express from 'express';
|
|
143
|
+
import cookieParser from 'cookie-parser';
|
|
144
|
+
import { createAuthMiddleware } from '@cleverclouds/auth-client/express';
|
|
145
|
+
import { auth } from './lib/auth.js';
|
|
146
|
+
|
|
147
|
+
const app = express();
|
|
148
|
+
app.use(express.json());
|
|
149
|
+
app.use(cookieParser()); // REQUIRED: cookie-parser must precede auth middleware
|
|
150
|
+
|
|
151
|
+
// Initialize middleware adapter
|
|
152
|
+
const {
|
|
153
|
+
requireAuth,
|
|
154
|
+
requirePermission,
|
|
155
|
+
requireRole,
|
|
156
|
+
handleCallback,
|
|
157
|
+
handleLogout,
|
|
158
|
+
} = createAuthMiddleware(auth, {
|
|
159
|
+
cookieName: 'projects_session',
|
|
160
|
+
cookieOptions: {
|
|
161
|
+
httpOnly: true,
|
|
162
|
+
secure: process.env.NODE_ENV === 'production',
|
|
163
|
+
sameSite: 'lax',
|
|
164
|
+
path: '/',
|
|
165
|
+
maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// 1. SSO Callback Endpoint
|
|
170
|
+
// Redeems single-use ticket, signs session cookie, and safely redirects to returnTo
|
|
171
|
+
app.get('/api/auth/callback', handleCallback({
|
|
172
|
+
defaultReturnTo: '/dashboard',
|
|
173
|
+
allowedRedirectHosts: ['projects.cleverclouds.in', 'localhost:3001'],
|
|
174
|
+
}));
|
|
175
|
+
|
|
176
|
+
// 2. Logout Endpoint
|
|
177
|
+
// Clears session cookie and redirects user
|
|
178
|
+
app.get('/logout', handleLogout({
|
|
179
|
+
redirectTo: '/login',
|
|
180
|
+
}));
|
|
181
|
+
|
|
182
|
+
// 3. Authenticated Route
|
|
183
|
+
// Attaches req.auth: VerificationResult
|
|
184
|
+
app.get('/api/me', requireAuth(), (req, res) => {
|
|
185
|
+
res.json({
|
|
186
|
+
user: req.auth.user,
|
|
187
|
+
roles: req.auth.roles,
|
|
188
|
+
permissions: req.auth.permissions,
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// 4. Permission-Gated Route
|
|
193
|
+
// Supports exact match or wildcards ('projects.*')
|
|
194
|
+
app.post('/api/projects', requirePermission('projects.create'), (req, res) => {
|
|
195
|
+
res.json({ message: 'Project created by ' + req.auth.user.email });
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// 5. Role-Gated Route
|
|
199
|
+
// Evaluates application roles case-insensitively
|
|
200
|
+
app.delete('/api/projects/:id', requireRole('project_admin'), (req, res) => {
|
|
201
|
+
res.json({ message: 'Project deleted' });
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
app.listen(3001, () => {
|
|
205
|
+
console.log('Downstream service listening on http://localhost:3001');
|
|
206
|
+
});
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## Next.js App Router Integration
|
|
212
|
+
|
|
213
|
+
### 1. Server Components & Pages
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
// app/dashboard/page.tsx
|
|
217
|
+
import { redirect } from 'next/navigation';
|
|
218
|
+
import { getServerSession } from '@cleverclouds/auth-client/nextjs';
|
|
219
|
+
import { auth } from '@/lib/auth';
|
|
220
|
+
|
|
221
|
+
export default async function DashboardPage() {
|
|
222
|
+
const session = await getServerSession(auth, { cookieName: 'projects_session' });
|
|
223
|
+
|
|
224
|
+
if (!session) {
|
|
225
|
+
redirect(auth.getAuthorizationUrl({ returnTo: '/dashboard' }));
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return (
|
|
229
|
+
<div className="p-8">
|
|
230
|
+
<h1 className="text-2xl font-bold">Welcome, {session.user.name}</h1>
|
|
231
|
+
<p className="text-gray-600">Email: {session.user.email}</p>
|
|
232
|
+
<div className="mt-4">
|
|
233
|
+
<span className="font-semibold">Assigned Roles:</span>
|
|
234
|
+
<span className="ml-2 bg-blue-100 text-blue-800 px-2 py-1 rounded">
|
|
235
|
+
{session.roles.join(', ')}
|
|
236
|
+
</span>
|
|
237
|
+
</div>
|
|
238
|
+
</div>
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### 2. Route Handlers (`protectRoute`)
|
|
244
|
+
|
|
245
|
+
```typescript
|
|
246
|
+
// app/api/projects/route.ts
|
|
247
|
+
import { NextResponse } from 'next/server';
|
|
248
|
+
import { protectRoute } from '@cleverclouds/auth-client/nextjs';
|
|
249
|
+
import { auth } from '@/lib/auth';
|
|
250
|
+
|
|
251
|
+
export async function POST(req: Request) {
|
|
252
|
+
const { authorized, session, response } = await protectRoute(auth, {
|
|
253
|
+
request: req,
|
|
254
|
+
permission: 'projects.create',
|
|
255
|
+
cookieName: 'projects_session',
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
if (!authorized) {
|
|
259
|
+
return response!; // Returns 401 Unauthorized or 403 Forbidden JSON
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const payload = await req.json();
|
|
263
|
+
return NextResponse.json({
|
|
264
|
+
success: true,
|
|
265
|
+
createdItem: payload,
|
|
266
|
+
createdBy: session!.user.email,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### 3. Next.js SSO Callback Route
|
|
272
|
+
|
|
273
|
+
```typescript
|
|
274
|
+
// app/api/auth/callback/route.ts
|
|
275
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
276
|
+
import { auth } from '@/lib/auth';
|
|
277
|
+
|
|
278
|
+
export async function GET(req: NextRequest) {
|
|
279
|
+
const ticket = req.nextUrl.searchParams.get('ticket');
|
|
280
|
+
const returnTo = req.nextUrl.searchParams.get('returnTo') || '/dashboard';
|
|
281
|
+
|
|
282
|
+
if (!ticket) {
|
|
283
|
+
return NextResponse.json({ error: 'Missing authorization ticket' }, { status: 400 });
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const exchange = await auth.exchangeTicket(ticket);
|
|
287
|
+
if (!exchange.success) {
|
|
288
|
+
return NextResponse.json({ error: exchange.error.message }, { status: 401 });
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const signedCookie = auth.signSessionCookie(exchange.data, {
|
|
292
|
+
expiresInSeconds: 7 * 24 * 60 * 60,
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
// Strict relative redirect enforcement
|
|
296
|
+
const safeRedirect = returnTo.startsWith('/') && !returnTo.startsWith('//')
|
|
297
|
+
? returnTo
|
|
298
|
+
: '/dashboard';
|
|
299
|
+
|
|
300
|
+
const res = NextResponse.redirect(new URL(safeRedirect, req.url));
|
|
301
|
+
res.cookies.set('projects_session', signedCookie, {
|
|
302
|
+
httpOnly: true,
|
|
303
|
+
secure: process.env.NODE_ENV === 'production',
|
|
304
|
+
sameSite: 'lax',
|
|
305
|
+
path: '/',
|
|
306
|
+
maxAge: 7 * 24 * 60 * 60,
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
return res;
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## Fastify / Raw Node.js Integration
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
import Fastify from 'fastify';
|
|
319
|
+
import fastifyCookie from '@fastify/cookie';
|
|
320
|
+
import { auth } from './lib/auth.js';
|
|
321
|
+
|
|
322
|
+
const app = Fastify();
|
|
323
|
+
app.register(fastifyCookie);
|
|
324
|
+
|
|
325
|
+
// Authentication Hook
|
|
326
|
+
app.addHook('preHandler', async (req, reply) => {
|
|
327
|
+
const cookie = req.cookies.projects_session;
|
|
328
|
+
if (!cookie) return;
|
|
329
|
+
|
|
330
|
+
const session = auth.verifySessionCookie(cookie);
|
|
331
|
+
if (session) {
|
|
332
|
+
(req as any).auth = session;
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
app.get('/api/projects', async (req, reply) => {
|
|
337
|
+
const authUser = (req as any).auth;
|
|
338
|
+
if (!authUser) {
|
|
339
|
+
return reply.status(401).send({ error: 'Unauthorized' });
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (!auth.hasPermission(authUser.permissions, 'projects.read')) {
|
|
343
|
+
return reply.status(403).send({ error: 'Forbidden' });
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return { projects: [] };
|
|
347
|
+
});
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
## Frontend (SPA) Integration Pattern
|
|
353
|
+
|
|
354
|
+
For Single-Page Applications (Vite / React / Vue / Angular):
|
|
355
|
+
1. **Login Trigger**: When the user clicks "Log In", navigate the browser to:
|
|
356
|
+
```javascript
|
|
357
|
+
window.location.href = `https://auth.cleverclouds.in/login?app=projects&returnTo=${encodeURIComponent('https://projects.cleverclouds.in/api/auth/callback')}`;
|
|
358
|
+
```
|
|
359
|
+
2. **Callback Handling**: Your backend API (Express or Next.js) handles `/api/auth/callback`, redeems the ticket, sets the `HttpOnly` signed cookie, and redirects the browser back to `https://projects.cleverclouds.in/dashboard`.
|
|
360
|
+
3. **Session Check**: The SPA calls `GET /api/me` on startup. The browser includes the `HttpOnly` cookie automatically. The backend returns the current user profile, roles, and permissions.
|
|
361
|
+
|
|
362
|
+
---
|
|
363
|
+
|
|
364
|
+
## Role-Based Access Control (RBAC)
|
|
365
|
+
|
|
366
|
+
The SDK provides both standalone evaluator functions and client instance methods.
|
|
367
|
+
|
|
368
|
+
```typescript
|
|
369
|
+
import {
|
|
370
|
+
hasPermission,
|
|
371
|
+
hasAnyPermission,
|
|
372
|
+
hasAllPermissions,
|
|
373
|
+
hasRole,
|
|
374
|
+
hasAnyRole,
|
|
375
|
+
} from '@cleverclouds/auth-client';
|
|
376
|
+
|
|
377
|
+
const userPermissions = ['projects.read', 'projects.create'];
|
|
378
|
+
|
|
379
|
+
// 1. Exact Match
|
|
380
|
+
hasPermission(userPermissions, 'projects.read'); // true
|
|
381
|
+
hasPermission(userPermissions, 'projects.delete'); // false
|
|
382
|
+
|
|
383
|
+
// 2. Resource Wildcard Matching
|
|
384
|
+
const managerPermissions = ['projects.*'];
|
|
385
|
+
hasPermission(managerPermissions, 'projects.create'); // true
|
|
386
|
+
hasPermission(managerPermissions, 'projects.delete'); // true
|
|
387
|
+
hasPermission(managerPermissions, 'billing.read'); // false
|
|
388
|
+
|
|
389
|
+
// 3. Platform Wildcard Matching
|
|
390
|
+
hasPermission(['*.*'], 'any.permission'); // true
|
|
391
|
+
hasPermission(['*'], 'any.permission'); // true
|
|
392
|
+
|
|
393
|
+
// 4. Role Evaluation (Case-Insensitive)
|
|
394
|
+
hasRole(['PROJECT_MANAGER'], 'project_manager'); // true
|
|
395
|
+
hasAnyRole(['DEVELOPER'], ['ADMIN', 'DEVELOPER']); // true
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
---
|
|
399
|
+
|
|
400
|
+
## Cryptographic Cookie Security
|
|
401
|
+
|
|
402
|
+
Session tokens stored in cookies are formatted as:
|
|
403
|
+
```
|
|
404
|
+
<base64url-encoded-payload>.<hex-hmac-sha256-signature>
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
- **HMAC-SHA256**: Keyed with `sessionSecret`.
|
|
408
|
+
- **Constant-Time Verification**: Verification uses Node.js `crypto.timingSafeEqual` to eliminate timing side-channel attacks.
|
|
409
|
+
- **Expiration Protection**: Every signed payload embeds an `exp` timestamp validated during verification.
|
|
410
|
+
- **Tamper Rejection**: Any modification of payload bytes or signature yields `null` without throwing.
|
|
411
|
+
|
|
412
|
+
---
|
|
413
|
+
|
|
414
|
+
## Error Handling
|
|
415
|
+
|
|
416
|
+
The client throws strongly-typed errors inheriting from `CleverCloudsAuthError`:
|
|
417
|
+
|
|
418
|
+
```typescript
|
|
419
|
+
import {
|
|
420
|
+
CleverCloudsAuthError,
|
|
421
|
+
AppAccessDeniedError,
|
|
422
|
+
InvalidTicketError,
|
|
423
|
+
InvalidCredentialsError,
|
|
424
|
+
} from '@cleverclouds/auth-client';
|
|
425
|
+
|
|
426
|
+
try {
|
|
427
|
+
const result = await auth.exchangeTicket(ticket);
|
|
428
|
+
} catch (err) {
|
|
429
|
+
if (err instanceof AppAccessDeniedError) {
|
|
430
|
+
console.error('User does not have permission to access this application');
|
|
431
|
+
} else if (err instanceof InvalidTicketError) {
|
|
432
|
+
console.error('Ticket has expired or was already consumed');
|
|
433
|
+
} else if (err instanceof InvalidCredentialsError) {
|
|
434
|
+
console.error('Invalid application API key');
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
---
|
|
440
|
+
|
|
441
|
+
## AI Agents & LLMs Guide
|
|
442
|
+
|
|
443
|
+
If you are developing with or configuring AI coding assistants (Claude Code, Cursor, GitHub Copilot, Antigravity, Devin):
|
|
444
|
+
- Consult [AGENTS.md](AGENTS.md) for concise, prompt-ready rules, schema definitions, and implementation checklists.
|
|
445
|
+
- Downstream repositories installing `@cleverclouds/auth-client` automatically receive `node_modules/@cleverclouds/auth-client/AGENTS.md`.
|
|
446
|
+
|
|
447
|
+
---
|
|
448
|
+
|
|
449
|
+
## License
|
|
450
|
+
|
|
451
|
+
MIT © CleverClouds
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { ClientConfig, VerificationResult, AuthorizationUrlOptions, CookieSignOptions } from './types.js';
|
|
2
|
+
import { hasPermission, hasAnyPermission, hasAllPermissions, hasRole, hasAnyRole } from './permissions.js';
|
|
3
|
+
import { CleverCloudsAuthError } from './errors.js';
|
|
4
|
+
export declare class CleverCloudsAuthClient {
|
|
5
|
+
readonly identityBaseUrl: string;
|
|
6
|
+
readonly applicationSlug: string;
|
|
7
|
+
readonly applicationApiKey: string;
|
|
8
|
+
readonly redirectUri: string;
|
|
9
|
+
readonly sessionSecret?: string;
|
|
10
|
+
constructor(config: ClientConfig);
|
|
11
|
+
/**
|
|
12
|
+
* Generates the SSO authorization redirect URL pointing to CleverClouds Identity.
|
|
13
|
+
*/
|
|
14
|
+
getAuthorizationUrl(options?: AuthorizationUrlOptions): string;
|
|
15
|
+
/**
|
|
16
|
+
* Generates the direct login initiation URL pointing to CleverClouds Identity.
|
|
17
|
+
*/
|
|
18
|
+
getLoginUrl(options?: AuthorizationUrlOptions): string;
|
|
19
|
+
/**
|
|
20
|
+
* Generates the Identity logout URL.
|
|
21
|
+
*/
|
|
22
|
+
getLogoutUrl(returnTo?: string): string;
|
|
23
|
+
/**
|
|
24
|
+
* Exchanges an authorization ticket with detailed success/error response.
|
|
25
|
+
*/
|
|
26
|
+
exchangeTicketDetailed(ticket: string): Promise<{
|
|
27
|
+
success: true;
|
|
28
|
+
data: VerificationResult;
|
|
29
|
+
} | {
|
|
30
|
+
success: false;
|
|
31
|
+
error: CleverCloudsAuthError;
|
|
32
|
+
}>;
|
|
33
|
+
/**
|
|
34
|
+
* Exchanges a single-use authorization ticket with CleverClouds Identity for
|
|
35
|
+
* the verified user profile, assigned roles, and application-scoped permissions.
|
|
36
|
+
*/
|
|
37
|
+
exchangeTicket(ticket: string): Promise<VerificationResult | null>;
|
|
38
|
+
/**
|
|
39
|
+
* Verifies an active session token directly against CleverClouds Identity.
|
|
40
|
+
*/
|
|
41
|
+
verifySession(sessionToken: string): Promise<VerificationResult | null>;
|
|
42
|
+
/**
|
|
43
|
+
* Encodes and cryptographically signs a session payload for storage in downstream cookies.
|
|
44
|
+
*/
|
|
45
|
+
signSessionCookie(result: VerificationResult, options?: CookieSignOptions): string;
|
|
46
|
+
/**
|
|
47
|
+
* Verifies an incoming session cookie value, enforcing HMAC integrity via sessionSecret.
|
|
48
|
+
*/
|
|
49
|
+
verifySessionCookie(cookieValue: string): VerificationResult | null;
|
|
50
|
+
hasPermission(permissions: string[], required: string): boolean;
|
|
51
|
+
hasAnyPermission(permissions: string[], requiredList: string[]): boolean;
|
|
52
|
+
hasAllPermissions(permissions: string[], requiredList: string[]): boolean;
|
|
53
|
+
hasRole(roles: string[], roleSlug: string): boolean;
|
|
54
|
+
hasAnyRole(roles: string[], roleSlugs: string[]): boolean;
|
|
55
|
+
static hasPermission: typeof hasPermission;
|
|
56
|
+
static hasAnyPermission: typeof hasAnyPermission;
|
|
57
|
+
static hasAllPermissions: typeof hasAllPermissions;
|
|
58
|
+
static hasRole: typeof hasRole;
|
|
59
|
+
static hasAnyRole: typeof hasAnyRole;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EACZ,kBAAkB,EAClB,uBAAuB,EACvB,iBAAiB,EAClB,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,iBAAiB,EACjB,OAAO,EACP,UAAU,EACX,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,qBAAqB,EAItB,MAAM,aAAa,CAAC;AAErB,qBAAa,sBAAsB;IACjC,SAAgB,eAAe,EAAE,MAAM,CAAC;IACxC,SAAgB,eAAe,EAAE,MAAM,CAAC;IACxC,SAAgB,iBAAiB,EAAE,MAAM,CAAC;IAC1C,SAAgB,WAAW,EAAE,MAAM,CAAC;IACpC,SAAgB,aAAa,CAAC,EAAE,MAAM,CAAC;gBAE3B,MAAM,EAAE,YAAY;IAkBhC;;OAEG;IACI,mBAAmB,CAAC,OAAO,GAAE,uBAA4B,GAAG,MAAM;IAUzE;;OAEG;IACI,WAAW,CAAC,OAAO,GAAE,uBAA4B,GAAG,MAAM;IAUjE;;OAEG;IACI,YAAY,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM;IAQ9C;;OAEG;IACU,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CACxD;QAAE,OAAO,EAAE,IAAI,CAAC;QAAC,IAAI,EAAE,kBAAkB,CAAA;KAAE,GAC3C;QAAE,OAAO,EAAE,KAAK,CAAC;QAAC,KAAK,EAAE,qBAAqB,CAAA;KAAE,CACnD;IAgFD;;;OAGG;IACU,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC;IAK/E;;OAEG;IACU,aAAa,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC;IAiCpF;;OAEG;IACI,iBAAiB,CAAC,MAAM,EAAE,kBAAkB,EAAE,OAAO,GAAE,iBAAsB,GAAG,MAAM;IAO7F;;OAEG;IACI,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,kBAAkB,GAAG,IAAI;IAYnE,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO;IAI/D,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,OAAO;IAIxE,iBAAiB,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,OAAO;IAIzE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO;IAInD,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,OAAO;IAMhE,OAAc,aAAa,uBAAiB;IAC5C,OAAc,gBAAgB,0BAAoB;IAClD,OAAc,iBAAiB,2BAAqB;IACpD,OAAc,OAAO,iBAAW;IAChC,OAAc,UAAU,oBAAc;CACvC"}
|