@nano-app/backend 0.13.0 → 0.14.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 +7 -10
- package/build/index.d.ts +1 -3
- package/build/index.js +57 -48
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -87,7 +87,7 @@ Every option is optional.
|
|
|
87
87
|
| `mainPlugin` | `{ routesDir: "src/routes" }` | Your routes (`routesDir`, relative to `cwd`), `basePath` (e.g. `/api`, no trailing slash) and database: see [Database](#database). |
|
|
88
88
|
| `plugins` | `[]` | Plugins mounted around your routes, in order: see [Plugins](#plugins). |
|
|
89
89
|
|
|
90
|
-
`packages` accepts `morgan`, `cors`, `helmet`, `compression`, `express` (`express.json` options)
|
|
90
|
+
`packages` accepts `morgan`, `cors`, `helmet`, `compression`, `express` (`express.json` options) and `urlencoded`. The merge is **per section**: passing one key keeps the others; pass `false` to disable one.
|
|
91
91
|
|
|
92
92
|
`morgan` logs `:timestamp | :method :path :status` by default: `:path` is the path without its query string, so tokens sent there stay out of the logs. Set `format` with `:url` to log the full URL.
|
|
93
93
|
|
|
@@ -166,14 +166,14 @@ export default createRoute<PrismaClient>({
|
|
|
166
166
|
|
|
167
167
|
## Better Auth
|
|
168
168
|
|
|
169
|
-
Users, sign-in and sessions with [Better Auth](https://www.better-auth.com), on your database.
|
|
169
|
+
Users, sign-in and sessions with [Better Auth](https://www.better-auth.com), on your database. On when your app has `src/auth.ts` (or the built `src/auth.js`), relative to `cwd`: its default export holds the options in `options` (everything but `database`: nano-app passes your Prisma client). Without the file it stays off.
|
|
170
170
|
|
|
171
171
|
```bash
|
|
172
172
|
pnpm add better-auth
|
|
173
173
|
```
|
|
174
174
|
|
|
175
175
|
```ts
|
|
176
|
-
// src/auth.ts:
|
|
176
|
+
// src/auth.ts: nano-app and the Better Auth CLI read `options`
|
|
177
177
|
import type { BetterAuthOptions } from 'better-auth';
|
|
178
178
|
|
|
179
179
|
export default {
|
|
@@ -184,22 +184,18 @@ export default {
|
|
|
184
184
|
```ts
|
|
185
185
|
// src/app.ts
|
|
186
186
|
import { createNanoApp } from '@nano-app/backend';
|
|
187
|
-
import auth from './auth';
|
|
188
187
|
|
|
189
|
-
createNanoApp({
|
|
190
|
-
packages: { betterAuth: auth.options },
|
|
191
|
-
mainPlugin: { database: { enabled: true } },
|
|
192
|
-
});
|
|
188
|
+
createNanoApp({ mainPlugin: { database: { enabled: true } } });
|
|
193
189
|
```
|
|
194
190
|
|
|
195
191
|
Its tables are yours, like the rest of your schema. Add its models with its CLI, then apply them with Prisma; run both again when you change the options:
|
|
196
192
|
|
|
197
193
|
```bash
|
|
198
|
-
npx auth generate --
|
|
194
|
+
npx auth generate --adapter prisma --dialect postgresql --output src/prisma/schema.prisma
|
|
199
195
|
npx prisma migrate dev
|
|
200
196
|
```
|
|
201
197
|
|
|
202
|
-
- It needs the database: `
|
|
198
|
+
- It needs the database: `src/auth.ts` without `mainPlugin.database.enabled` fails at startup, and so does a file without `options`.
|
|
203
199
|
- Startup also fails if your schema lacks a model the options need (`user`, `session`, ...), naming them.
|
|
204
200
|
- Its endpoints are mounted with your routes, at its `basePath` (default `/api/auth`), and stay public.
|
|
205
201
|
- In production, Better Auth limits sign-in, sign-up and password reset to a few attempts per client IP (its `rateLimit`): see [Client IP](#client-ip).
|
|
@@ -213,6 +209,7 @@ npx prisma migrate dev
|
|
|
213
209
|
- `createRoute({ public: true, ... })` answers without a session; its handlers still receive `session` when the request has one. An unknown path answers 404.
|
|
214
210
|
- Its own endpoints, the plugins mounted before the app and the middlewares a plugin adds with `app.use` in `onMount` are not checked: a plugin that exposes something public says it in its README.
|
|
215
211
|
- Better Auth is loaded only when it is on.
|
|
212
|
+
- Before 0.14.0 the options went in `packages.betterAuth`: move them to the default export of `src/auth.ts`.
|
|
216
213
|
- nano-app creates the one instance the app uses: do not call `betterAuth()` in `src/auth.ts`. The CLI's `generate` only reads `options` of its default export.
|
|
217
214
|
|
|
218
215
|
### Permissions
|
package/build/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import express, { Express, Request, Request as Request$1 } from "express";
|
|
|
2
2
|
import { CorsOptions } from "cors";
|
|
3
3
|
import "dotenv/config";
|
|
4
4
|
import "jiti";
|
|
5
|
-
import
|
|
5
|
+
import "better-auth";
|
|
6
6
|
/** API response */
|
|
7
7
|
type ApiResponse<T = any> = {
|
|
8
8
|
status?: number;
|
|
@@ -142,8 +142,6 @@ type PackagesConfig = {
|
|
|
142
142
|
express?: Partial<Parameters<typeof express.json>[0]> | false;
|
|
143
143
|
/** urlencoded configuration */
|
|
144
144
|
urlencoded?: Partial<Parameters<typeof express.urlencoded>[0]> | false;
|
|
145
|
-
/** Better Auth options, without `database`: it uses the app's Prisma client. Defaults to false */
|
|
146
|
-
betterAuth?: Omit<BetterAuthOptions, 'database'> | false;
|
|
147
145
|
};
|
|
148
146
|
/** Application configuration */
|
|
149
147
|
type NanoAppConfig = {
|
package/build/index.js
CHANGED
|
@@ -5,9 +5,9 @@ import cors from "cors";
|
|
|
5
5
|
import helmet from "helmet";
|
|
6
6
|
import compression from "compression";
|
|
7
7
|
import "dotenv/config";
|
|
8
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
8
9
|
import { glob } from "glob";
|
|
9
10
|
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
10
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
11
11
|
import { createJiti } from "jiti";
|
|
12
12
|
import { execFileSync } from "node:child_process";
|
|
13
13
|
import { createHash } from "node:crypto";
|
|
@@ -165,6 +165,57 @@ var FileLoader = class {
|
|
|
165
165
|
return Validator.validate(oEntries, extension);
|
|
166
166
|
}
|
|
167
167
|
};
|
|
168
|
+
// src/auth/BetterAuth.ts
|
|
169
|
+
/** Files of the app's Better Auth options, relative to `cwd`: source, then built */
|
|
170
|
+
const AUTH_FILES = ["src/auth.ts", "src/auth.js"];
|
|
171
|
+
/** Better Auth on the app's Prisma client: the instance, its handler and the session lookup */
|
|
172
|
+
var BetterAuth = class {
|
|
173
|
+
static oAuth;
|
|
174
|
+
/** The options of the app's `src/auth.ts`, or undefined without it: Better Auth stays off */
|
|
175
|
+
static async findOptions() {
|
|
176
|
+
const sFilePath = AUTH_FILES.find(existsSync);
|
|
177
|
+
if (!sFilePath) return void 0;
|
|
178
|
+
const oFile = await FileReader.loadModule(sFilePath, { default: true });
|
|
179
|
+
if (!oFile?.options) throw new Error(`[nano-app] [ERROR] ${sFilePath} must default export { options }: the Better Auth options.`);
|
|
180
|
+
return oFile.options;
|
|
181
|
+
}
|
|
182
|
+
/** The instance, once the app's plugin created it */
|
|
183
|
+
static get() {
|
|
184
|
+
return this.oAuth;
|
|
185
|
+
}
|
|
186
|
+
/** Creates the instance on the app's client, its handler and the session lookup */
|
|
187
|
+
static async create(oOptions, oDb) {
|
|
188
|
+
const { betterAuth } = await import("better-auth");
|
|
189
|
+
const { prismaAdapter } = await import("better-auth/adapters/prisma");
|
|
190
|
+
const { getSchema } = await import("better-auth/db");
|
|
191
|
+
const { fromNodeHeaders, toNodeHandler } = await import("better-auth/node");
|
|
192
|
+
const aMissing = Object.keys(getSchema(oOptions)).filter((sModel) => !oDb[sModel]);
|
|
193
|
+
if (aMissing.length) throw new Error(`[nano-app] [ERROR] Better Auth needs the models ${aMissing.join(", ")} in the app's schema. Run \`npx auth generate\` and apply your schema with Prisma.`);
|
|
194
|
+
const oAuth = betterAuth({
|
|
195
|
+
...oOptions,
|
|
196
|
+
advanced: {
|
|
197
|
+
...oOptions.advanced,
|
|
198
|
+
ipAddress: {
|
|
199
|
+
trustedProxies: PRIVATE_NETWORKS,
|
|
200
|
+
...oOptions.advanced?.ipAddress
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
database: prismaAdapter(oDb, { provider: "postgresql" })
|
|
204
|
+
});
|
|
205
|
+
this.oAuth = oAuth;
|
|
206
|
+
const fnSession = (oReq, oRes, fnNext) => {
|
|
207
|
+
oAuth.api.getSession({ headers: fromNodeHeaders(oReq.headers) }).then((oSession) => {
|
|
208
|
+
oRes.locals.session = oSession ?? void 0;
|
|
209
|
+
fnNext();
|
|
210
|
+
}).catch(fnNext);
|
|
211
|
+
};
|
|
212
|
+
return {
|
|
213
|
+
oHandler: toNodeHandler(oAuth),
|
|
214
|
+
fnSession,
|
|
215
|
+
sPath: `${oOptions.basePath ?? "/api/auth"}/{*path}`
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
};
|
|
168
219
|
// src/auth/permissions.ts
|
|
169
220
|
/** Whether the session's user has the permissions; always without Better Auth or permissions */
|
|
170
221
|
async function isGranted({ auth, session }, oPermissions) {
|
|
@@ -196,7 +247,7 @@ var Endpoint = class {
|
|
|
196
247
|
}
|
|
197
248
|
/** Handler for all methods */
|
|
198
249
|
async mount(oParams, sPath) {
|
|
199
|
-
if (!oParams.auth && Object.values(this.oMethods).some(({ permissions }) => permissions)) console.warn(`[nano-app] [WARNING] ${sPath}: its permissions are not checked without Better Auth.
|
|
250
|
+
if (!oParams.auth && Object.values(this.oMethods).some(({ permissions }) => permissions)) console.warn(`[nano-app] [WARNING] ${sPath}: its permissions are not checked without Better Auth. Add src/auth.ts.`);
|
|
200
251
|
await this.oCallbacks.onInit?.(oParams);
|
|
201
252
|
return this.createHandler(oParams);
|
|
202
253
|
}
|
|
@@ -272,47 +323,6 @@ var Router = class {
|
|
|
272
323
|
return `${this.sBasePath}${sEndpoint.replace(/\/index$/, "")}${sExpression}` || "/";
|
|
273
324
|
}
|
|
274
325
|
};
|
|
275
|
-
// src/auth/BetterAuth.ts
|
|
276
|
-
/** Better Auth on the app's Prisma client: the instance, its handler and the session lookup */
|
|
277
|
-
var BetterAuth = class {
|
|
278
|
-
static oAuth;
|
|
279
|
-
/** The instance, once the app's plugin created it */
|
|
280
|
-
static get() {
|
|
281
|
-
return this.oAuth;
|
|
282
|
-
}
|
|
283
|
-
/** Creates the instance on the app's client, its handler and the session lookup */
|
|
284
|
-
static async create(oOptions, oDb) {
|
|
285
|
-
const { betterAuth } = await import("better-auth");
|
|
286
|
-
const { prismaAdapter } = await import("better-auth/adapters/prisma");
|
|
287
|
-
const { getSchema } = await import("better-auth/db");
|
|
288
|
-
const { fromNodeHeaders, toNodeHandler } = await import("better-auth/node");
|
|
289
|
-
const aMissing = Object.keys(getSchema(oOptions)).filter((sModel) => !oDb[sModel]);
|
|
290
|
-
if (aMissing.length) throw new Error(`[nano-app] [ERROR] Better Auth needs the models ${aMissing.join(", ")} in the app's schema. Run \`npx auth generate\` and apply your schema with Prisma.`);
|
|
291
|
-
const oAuth = betterAuth({
|
|
292
|
-
...oOptions,
|
|
293
|
-
advanced: {
|
|
294
|
-
...oOptions.advanced,
|
|
295
|
-
ipAddress: {
|
|
296
|
-
trustedProxies: PRIVATE_NETWORKS,
|
|
297
|
-
...oOptions.advanced?.ipAddress
|
|
298
|
-
}
|
|
299
|
-
},
|
|
300
|
-
database: prismaAdapter(oDb, { provider: "postgresql" })
|
|
301
|
-
});
|
|
302
|
-
this.oAuth = oAuth;
|
|
303
|
-
const fnSession = (oReq, oRes, fnNext) => {
|
|
304
|
-
oAuth.api.getSession({ headers: fromNodeHeaders(oReq.headers) }).then((oSession) => {
|
|
305
|
-
oRes.locals.session = oSession ?? void 0;
|
|
306
|
-
fnNext();
|
|
307
|
-
}).catch(fnNext);
|
|
308
|
-
};
|
|
309
|
-
return {
|
|
310
|
-
oHandler: toNodeHandler(oAuth),
|
|
311
|
-
fnSession,
|
|
312
|
-
sPath: `${oOptions.basePath ?? "/api/auth"}/{*path}`
|
|
313
|
-
};
|
|
314
|
-
}
|
|
315
|
-
};
|
|
316
326
|
// src/database/Prisma.ts
|
|
317
327
|
/** Runs the consumer's Prisma CLI: generates clients and applies official plugins' migrations */
|
|
318
328
|
var Prisma = class {
|
|
@@ -462,7 +472,7 @@ var Plugin = class {
|
|
|
462
472
|
const { name, routesDir, basePath, database, options, callbacks } = this.oConfig;
|
|
463
473
|
const oDb = database?.enabled ? await Database.mount(name, database) : Database.get("app");
|
|
464
474
|
if (this.oAuthOptions) {
|
|
465
|
-
if (!oDb) throw new Error("[nano-app] [ERROR]
|
|
475
|
+
if (!oDb) throw new Error("[nano-app] [ERROR] src/auth.ts needs the database of the app: set mainPlugin.database.enabled to true.");
|
|
466
476
|
const { sPath, oHandler, fnSession } = await BetterAuth.create(this.oAuthOptions, oDb);
|
|
467
477
|
oApp.all(sPath, oHandler);
|
|
468
478
|
oApp.use(fnSession);
|
|
@@ -506,8 +516,7 @@ const INITIAL_CONFIG = {
|
|
|
506
516
|
helmet: true,
|
|
507
517
|
compression: true,
|
|
508
518
|
express: { limit: "10kb" },
|
|
509
|
-
urlencoded: { extended: false }
|
|
510
|
-
betterAuth: false
|
|
519
|
+
urlencoded: { extended: false }
|
|
511
520
|
},
|
|
512
521
|
/** Main plugin to mount */
|
|
513
522
|
mainPlugin: {
|
|
@@ -643,8 +652,8 @@ var NanoApp = class {
|
|
|
643
652
|
};
|
|
644
653
|
for (const oPlugin of this.oConfig.plugins) oPlugins[oPlugin.stage ?? "before"].push(oPlugin);
|
|
645
654
|
for (const oPlugin of oPlugins.before) await new Plugin(oPlugin).mount(this.oApp);
|
|
646
|
-
const { mainPlugin
|
|
647
|
-
await new Plugin(mainPlugin,
|
|
655
|
+
const { mainPlugin } = this.oConfig;
|
|
656
|
+
await new Plugin(mainPlugin, await BetterAuth.findOptions()).mount(this.oApp);
|
|
648
657
|
for (const oPlugin of oPlugins.after) await new Plugin(oPlugin).mount(this.oApp);
|
|
649
658
|
this.oApp.use(notFoundHandler);
|
|
650
659
|
this.oApp.use(errorHandler);
|