@ibertran/fortytwo-intra-client 4.1.0 → 5.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/README.md CHANGED
@@ -20,6 +20,26 @@ const user = await client.get("users/ibertran");
20
20
  console.log(user);
21
21
  ```
22
22
 
23
+ ## Intra V3
24
+
25
+ The library also exports `FortytwoIntraV3Client` for staff-only Intra V3 endpoints that use the 42 Auth token flow.
26
+
27
+ ```typescript
28
+ import { FortytwoIntraV3Client } from "@ibertran/fortytwo-intra-client";
29
+
30
+ const client = new FortytwoIntraV3Client({
31
+ client_id: "staff-42-campus",
32
+ client_secret: "your_client_secret",
33
+ username: "your_staff_login",
34
+ password: "your_staff_password",
35
+ });
36
+
37
+ const response = await client.get("some/v3/endpoint");
38
+ console.log(response);
39
+ ```
40
+
41
+ The V3 client automatically requests a bearer token, reuses it while valid, and falls back to the refresh token when available.
42
+
23
43
  ## Configuration
24
44
 
25
45
  The `FortytwoIntraClient` class accepts three parameters:
@@ -0,0 +1,8 @@
1
+ import { AxiosError } from "axios";
2
+ export declare class FortytwoIntraClientHttpError extends Error {
3
+ statusCode: number;
4
+ statusText: string;
5
+ data: unknown;
6
+ constructor(error: AxiosError);
7
+ }
8
+ export declare function isFortytwoIntraClientHttpError(error: unknown): error is FortytwoIntraClientHttpError;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FortytwoIntraClientHttpError = void 0;
4
+ exports.isFortytwoIntraClientHttpError = isFortytwoIntraClientHttpError;
5
+ class FortytwoIntraClientHttpError extends Error {
6
+ statusCode;
7
+ statusText;
8
+ data;
9
+ constructor(error) {
10
+ const method = error.config?.method?.toUpperCase() ?? null;
11
+ const route = error.config?.url ?? null;
12
+ const statusCode = error.response?.status ?? NaN;
13
+ const statusText = error.response?.statusText ?? "Unknown";
14
+ const message = method && route ? `${method} ${route} - HTTP ${statusCode} ${statusText}` : error.message;
15
+ super(message);
16
+ this.name = "FortytwoIntraClientHttpError";
17
+ this.statusCode = statusCode;
18
+ this.statusText = statusText;
19
+ this.data = error.response?.data;
20
+ }
21
+ }
22
+ exports.FortytwoIntraClientHttpError = FortytwoIntraClientHttpError;
23
+ function isFortytwoIntraClientHttpError(error) {
24
+ return error instanceof FortytwoIntraClientHttpError;
25
+ }
@@ -6,11 +6,7 @@ class FortytwoIntraClientValidationError extends Error {
6
6
  schemaErrors;
7
7
  data;
8
8
  constructor(schemaErrors, data) {
9
- // const errorMessages = schemaErrors.errors
10
- // .map((error) => `${error.path.join(".")} - ${error.message}`)
11
- // .join("; ");
12
- const message = `Schema validation failed: `;
13
- super(message);
9
+ super("Schema validation failed");
14
10
  this.name = "FortytwoIntraClientValidationError";
15
11
  this.schemaErrors = schemaErrors;
16
12
  this.data = data;
package/dist/index.d.ts CHANGED
@@ -1,6 +1,8 @@
1
+ import z from "zod";
1
2
  import { inputOptions } from "./types";
2
- import { FortytwoIntraClientError, isFortytwoIntraClientError } from "./lib/errors";
3
- import { z } from "zod";
3
+ export { FortytwoIntraV3Client } from "./intraV3";
4
+ export * from "./errors/httpError";
5
+ export * from "./errors/validationError";
4
6
  export interface FortytwoIntraClientConf {
5
7
  redirect_uri: string | null;
6
8
  base_url: string;
@@ -73,4 +75,3 @@ export declare class FortytwoIntraClient {
73
75
  exchangeOAuthCode(code: string, redirect_uri?: string): Promise<any>;
74
76
  tokenInfos(options?: Omit<inputOptions, "body" | "perPage" | "maxPages">): Promise<any>;
75
77
  }
76
- export { FortytwoIntraClientError, isFortytwoIntraClientError };
package/dist/index.js CHANGED
@@ -32,20 +32,25 @@ var __importStar = (this && this.__importStar) || (function () {
32
32
  return result;
33
33
  };
34
34
  })();
35
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
36
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
37
+ };
35
38
  var __importDefault = (this && this.__importDefault) || function (mod) {
36
39
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
40
  };
38
41
  Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.isFortytwoIntraClientError = exports.FortytwoIntraClientError = exports.FortytwoIntraClient = void 0;
42
+ exports.FortytwoIntraClient = exports.FortytwoIntraV3Client = void 0;
40
43
  const axios_1 = __importStar(require("axios"));
41
44
  const axios_rate_limit_1 = __importDefault(require("axios-rate-limit"));
42
- const pagination_1 = require("./lib/pagination");
45
+ const zod_1 = __importDefault(require("zod"));
43
46
  const logs_1 = require("./lib/logs");
44
- const errors_1 = require("./lib/errors");
45
- Object.defineProperty(exports, "FortytwoIntraClientError", { enumerable: true, get: function () { return errors_1.FortytwoIntraClientError; } });
46
- Object.defineProperty(exports, "isFortytwoIntraClientError", { enumerable: true, get: function () { return errors_1.isFortytwoIntraClientError; } });
47
- const zod_1 = require("zod");
47
+ const pagination_1 = require("./lib/pagination");
48
+ const httpError_1 = require("./errors/httpError");
48
49
  const validationError_1 = require("./errors/validationError");
50
+ var intraV3_1 = require("./intraV3");
51
+ Object.defineProperty(exports, "FortytwoIntraV3Client", { enumerable: true, get: function () { return intraV3_1.FortytwoIntraV3Client; } });
52
+ __exportStar(require("./errors/httpError"), exports);
53
+ __exportStar(require("./errors/validationError"), exports);
49
54
  const defaultConf = {
50
55
  redirect_uri: null,
51
56
  base_url: "https://api.intra.42.fr/v2/",
@@ -156,7 +161,7 @@ class FortytwoIntraClient {
156
161
  return this.reqHandler(url, options);
157
162
  }
158
163
  else {
159
- throw new errors_1.FortytwoIntraClientError(err);
164
+ throw new httpError_1.FortytwoIntraClientHttpError(err);
160
165
  }
161
166
  }
162
167
  else {
@@ -191,7 +196,7 @@ class FortytwoIntraClient {
191
196
  return schema.parse(data);
192
197
  }
193
198
  catch (err) {
194
- if (err instanceof zod_1.z.ZodError) {
199
+ if (err instanceof zod_1.default.ZodError) {
195
200
  throw new validationError_1.FortytwoIntraClientValidationError(err, data);
196
201
  }
197
202
  throw err;
@@ -0,0 +1,58 @@
1
+ import z from "zod";
2
+ import { inputOptions } from "../types";
3
+ export * from "../errors/httpError";
4
+ export * from "../errors/validationError";
5
+ export interface FortytwoIntraClientConf {
6
+ base_url: string | undefined;
7
+ token_url: string;
8
+ maxRetry: number;
9
+ logLine: boolean;
10
+ errLogBody: boolean;
11
+ }
12
+ type Credentials = {
13
+ client_id: string;
14
+ client_secret: string;
15
+ username: string;
16
+ password: string;
17
+ };
18
+ export declare class FortytwoIntraV3Client {
19
+ private credentials;
20
+ private base_url;
21
+ private token_url;
22
+ private axiosInstance;
23
+ private retryOn;
24
+ private maxRetry;
25
+ private logLine;
26
+ private errLogBody;
27
+ private authorization;
28
+ constructor(credentials: Credentials, conf?: Partial<FortytwoIntraClientConf>);
29
+ private generateToken;
30
+ private isTokenValid;
31
+ private isRefreshTokenValid;
32
+ private fetch;
33
+ private reqHandler;
34
+ private logSuccess;
35
+ private logError;
36
+ private validate;
37
+ get(endpoint: URL | string, options?: Omit<inputOptions, "body" | "perPage" | "maxPages" | "token">): Promise<any>;
38
+ get<S extends z.ZodType>(endpoint: URL | string, options: Omit<inputOptions, "body" | "perPage" | "maxPages" | "token"> & {
39
+ schema: S;
40
+ }): Promise<z.infer<S>>;
41
+ post(endpoint: URL | string, options?: Omit<inputOptions, "perPage" | "maxPages">): Promise<any>;
42
+ post<S extends z.ZodType>(endpoint: URL | string, options: Omit<inputOptions, "perPage" | "maxPages"> & {
43
+ schema: S;
44
+ }): Promise<z.infer<S>>;
45
+ put(endpoint: URL | string, options?: Omit<inputOptions, "perPage" | "maxPages">): Promise<any>;
46
+ put<S extends z.ZodType>(endpoint: URL | string, options: Omit<inputOptions, "perPage" | "maxPages"> & {
47
+ schema: S;
48
+ }): Promise<z.infer<S>>;
49
+ patch(endpoint: URL | string, options?: Omit<inputOptions, "perPage" | "maxPages">): Promise<any>;
50
+ patch<S extends z.ZodType>(endpoint: URL | string, options: Omit<inputOptions, "perPage" | "maxPages"> & {
51
+ schema: S;
52
+ }): Promise<z.infer<S>>;
53
+ delete(endpoint: URL | string, options?: Omit<inputOptions, "perPage" | "maxPages">): Promise<any>;
54
+ delete<S extends z.ZodType>(endpoint: URL | string, options: Omit<inputOptions, "perPage" | "maxPages"> & {
55
+ schema: S;
56
+ }): Promise<z.infer<S>>;
57
+ URL(endpoint: string): URL;
58
+ }
@@ -0,0 +1,266 @@
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
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
36
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
37
+ };
38
+ var __importDefault = (this && this.__importDefault) || function (mod) {
39
+ return (mod && mod.__esModule) ? mod : { "default": mod };
40
+ };
41
+ Object.defineProperty(exports, "__esModule", { value: true });
42
+ exports.FortytwoIntraV3Client = void 0;
43
+ const axios_1 = __importStar(require("axios"));
44
+ const zod_1 = __importDefault(require("zod"));
45
+ const logs_1 = require("../lib/logs");
46
+ const httpError_1 = require("../errors/httpError");
47
+ const validationError_1 = require("../errors/validationError");
48
+ const schemas_1 = require("./schemas");
49
+ __exportStar(require("../errors/httpError"), exports);
50
+ __exportStar(require("../errors/validationError"), exports);
51
+ const defaultConf = {
52
+ base_url: undefined,
53
+ token_url: "https://auth.42.fr/auth/realms/staff-42/protocol/openid-connect/token",
54
+ maxRetry: 5,
55
+ logLine: true,
56
+ errLogBody: true,
57
+ };
58
+ class FortytwoIntraV3Client {
59
+ credentials;
60
+ base_url;
61
+ token_url;
62
+ axiosInstance;
63
+ retryOn;
64
+ maxRetry;
65
+ logLine;
66
+ errLogBody;
67
+ authorization;
68
+ constructor(credentials, conf = {}) {
69
+ this.credentials = credentials;
70
+ const config = { ...defaultConf, ...conf };
71
+ this.base_url = config.base_url;
72
+ this.token_url = config.token_url;
73
+ this.axiosInstance = axios_1.default.create();
74
+ this.retryOn = [401, 429, 500];
75
+ this.maxRetry = config.maxRetry;
76
+ this.logLine = config.logLine;
77
+ this.errLogBody = config.errLogBody;
78
+ this.authorization = null;
79
+ }
80
+ async generateToken() {
81
+ const data = this.authorization && this.isRefreshTokenValid()
82
+ ? new URLSearchParams({
83
+ grant_type: "refresh_token",
84
+ refresh_token: this.authorization.refresh_token,
85
+ })
86
+ : new URLSearchParams({
87
+ grant_type: "password",
88
+ username: this.credentials.username,
89
+ password: this.credentials.password,
90
+ });
91
+ const res = await this.axiosInstance.post(this.token_url, data, {
92
+ headers: {
93
+ Authorization: `Basic ${Buffer.from(`${this.credentials.client_id}:${this.credentials.client_secret}`).toString("base64")}`,
94
+ "Content-Type": "application/x-www-form-urlencoded",
95
+ },
96
+ });
97
+ return schemas_1.tokenSchema.parse(res.data);
98
+ }
99
+ isTokenValid() {
100
+ return this.authorization !== null && this.authorization.expires_at - 30000 > Date.now();
101
+ }
102
+ isRefreshTokenValid() {
103
+ return (this.authorization !== null && this.authorization.refresh_expires_at - 30000 > Date.now());
104
+ }
105
+ async fetch(url, options) {
106
+ const { method, body, query } = options;
107
+ if (!this.isTokenValid()) {
108
+ this.authorization = await this.generateToken();
109
+ }
110
+ // Attach access_token
111
+ const accessToken = this.authorization?.access_token;
112
+ // Extract query parameters from URL and combine with options.query
113
+ const urlParams = {};
114
+ url.searchParams.forEach((value, key) => {
115
+ urlParams[key] = value;
116
+ });
117
+ // Combine URL params with query params (query params take precedence)
118
+ const combinedParams = { ...urlParams, ...query };
119
+ // Create clean URL without query parameters
120
+ const cleanUrl = new URL(url);
121
+ cleanUrl.search = "";
122
+ return this.axiosInstance.request({
123
+ method: method.toLowerCase(),
124
+ url: cleanUrl.toString(),
125
+ headers: {
126
+ Authorization: `Bearer ${accessToken}`,
127
+ },
128
+ params: combinedParams,
129
+ data: body ? body : undefined,
130
+ });
131
+ }
132
+ async reqHandler(url, options) {
133
+ try {
134
+ const res = await this.fetch(url, options);
135
+ this.logSuccess(res, options);
136
+ return res;
137
+ }
138
+ catch (err) {
139
+ if ((0, axios_1.isAxiosError)(err)) {
140
+ this.logError(err, options);
141
+ const { attempt, maxRetry } = options;
142
+ const status = err.response?.status;
143
+ if (maxRetry > 0 && attempt < maxRetry && status && this.retryOn.includes(status)) {
144
+ if (status === 401) {
145
+ this.authorization = null;
146
+ }
147
+ options.attempt++;
148
+ return this.reqHandler(url, options);
149
+ }
150
+ else {
151
+ throw new httpError_1.FortytwoIntraClientHttpError(err);
152
+ }
153
+ }
154
+ else {
155
+ throw err;
156
+ }
157
+ }
158
+ }
159
+ logSuccess(res, options) {
160
+ if (!options.logLine)
161
+ return;
162
+ const line = (0, logs_1.getLogLine)(res, options);
163
+ console.log(line);
164
+ }
165
+ logError(err, options) {
166
+ if (!options.logLine)
167
+ return;
168
+ const line = (0, logs_1.getErrorLogLine)(err, options);
169
+ const body = err.response?.data;
170
+ const logBody = options.errLogBody && body && Object.keys(body).length;
171
+ try {
172
+ console.log(line, logBody ? JSON.stringify(body, null, 2) : "");
173
+ }
174
+ catch {
175
+ console.log(line, logBody ? body : "");
176
+ }
177
+ }
178
+ validate(data, schema) {
179
+ if (!schema) {
180
+ return data;
181
+ }
182
+ try {
183
+ return schema.parse(data);
184
+ }
185
+ catch (err) {
186
+ if (err instanceof zod_1.default.ZodError) {
187
+ throw new validationError_1.FortytwoIntraClientValidationError(err, data);
188
+ }
189
+ throw err;
190
+ }
191
+ }
192
+ async get(endpoint, options = {}) {
193
+ if (endpoint instanceof URL === false) {
194
+ endpoint = new URL(endpoint, this.base_url);
195
+ }
196
+ const res = await this.reqHandler(endpoint, {
197
+ method: "GET",
198
+ attempt: 0,
199
+ maxRetry: this.maxRetry,
200
+ logLine: this.logLine,
201
+ errLogBody: this.errLogBody,
202
+ ...options,
203
+ });
204
+ return this.validate(res.data, options.schema);
205
+ }
206
+ async post(endpoint, options = {}) {
207
+ if (endpoint instanceof URL === false) {
208
+ endpoint = new URL(endpoint, this.base_url);
209
+ }
210
+ const res = await this.reqHandler(endpoint, {
211
+ method: "POST",
212
+ attempt: 0,
213
+ maxRetry: this.maxRetry,
214
+ logLine: this.logLine,
215
+ errLogBody: this.errLogBody,
216
+ ...options,
217
+ });
218
+ return this.validate(res.data, options.schema);
219
+ }
220
+ async put(endpoint, options = {}) {
221
+ if (endpoint instanceof URL === false) {
222
+ endpoint = new URL(endpoint, this.base_url);
223
+ }
224
+ const res = await this.reqHandler(endpoint, {
225
+ method: "PUT",
226
+ attempt: 0,
227
+ maxRetry: this.maxRetry,
228
+ logLine: this.logLine,
229
+ errLogBody: this.errLogBody,
230
+ ...options,
231
+ });
232
+ return this.validate(res.data, options.schema);
233
+ }
234
+ async patch(endpoint, options = {}) {
235
+ if (endpoint instanceof URL === false) {
236
+ endpoint = new URL(endpoint, this.base_url);
237
+ }
238
+ const res = await this.reqHandler(endpoint, {
239
+ method: "PATCH",
240
+ attempt: 0,
241
+ maxRetry: this.maxRetry,
242
+ logLine: this.logLine,
243
+ errLogBody: this.errLogBody,
244
+ ...options,
245
+ });
246
+ return this.validate(res.data, options.schema);
247
+ }
248
+ async delete(endpoint, options = {}) {
249
+ if (endpoint instanceof URL === false) {
250
+ endpoint = new URL(endpoint, this.base_url);
251
+ }
252
+ const res = await this.reqHandler(endpoint, {
253
+ method: "DELETE",
254
+ attempt: 0,
255
+ maxRetry: this.maxRetry,
256
+ logLine: this.logLine,
257
+ errLogBody: this.errLogBody,
258
+ ...options,
259
+ });
260
+ return this.validate(res.data, options.schema);
261
+ }
262
+ URL(endpoint) {
263
+ return new URL(endpoint, this.base_url);
264
+ }
265
+ }
266
+ exports.FortytwoIntraV3Client = FortytwoIntraV3Client;
@@ -0,0 +1,27 @@
1
+ import z from "zod";
2
+ export declare const tokenSchema: z.ZodPipe<z.ZodObject<{
3
+ access_token: z.ZodString;
4
+ expires_in: z.ZodNumber;
5
+ refresh_expires_in: z.ZodNumber;
6
+ refresh_token: z.ZodString;
7
+ token_type: z.ZodString;
8
+ "not-before-policy": z.ZodNumber;
9
+ session_state: z.ZodString;
10
+ scope: z.ZodString;
11
+ }, z.core.$strict>, z.ZodTransform<{
12
+ access_token: string;
13
+ expires_at: number;
14
+ refresh_token: string;
15
+ refresh_expires_at: number;
16
+ token_type: string;
17
+ }, {
18
+ access_token: string;
19
+ expires_in: number;
20
+ refresh_expires_in: number;
21
+ refresh_token: string;
22
+ token_type: string;
23
+ "not-before-policy": number;
24
+ session_state: string;
25
+ scope: string;
26
+ }>>;
27
+ export type Token = z.infer<typeof tokenSchema>;
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.tokenSchema = void 0;
7
+ const zod_1 = __importDefault(require("zod"));
8
+ exports.tokenSchema = zod_1.default
9
+ .strictObject({
10
+ access_token: zod_1.default.string(),
11
+ expires_in: zod_1.default.number(),
12
+ refresh_expires_in: zod_1.default.number(),
13
+ refresh_token: zod_1.default.string(),
14
+ token_type: zod_1.default.string(),
15
+ "not-before-policy": zod_1.default.number(),
16
+ session_state: zod_1.default.string(),
17
+ scope: zod_1.default.string(),
18
+ })
19
+ .transform((obj) => {
20
+ const now = Date.now();
21
+ return {
22
+ access_token: obj.access_token,
23
+ expires_at: now + obj.expires_in * 1000,
24
+ refresh_token: obj.refresh_token,
25
+ refresh_expires_at: now + obj.refresh_expires_in * 1000,
26
+ token_type: obj.token_type,
27
+ };
28
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ibertran/fortytwo-intra-client",
3
- "version": "4.1.0",
3
+ "version": "5.0.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "author": {