@nano-app/backend 0.12.5 → 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 +12 -13
- 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,39 +166,36 @@ 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:
|
|
177
|
-
import {
|
|
176
|
+
// src/auth.ts: nano-app and the Better Auth CLI read `options`
|
|
177
|
+
import type { BetterAuthOptions } from 'better-auth';
|
|
178
178
|
|
|
179
|
-
export
|
|
180
|
-
|
|
179
|
+
export default {
|
|
180
|
+
options: { emailAndPassword: { enabled: true } } satisfies BetterAuthOptions,
|
|
181
|
+
};
|
|
181
182
|
```
|
|
182
183
|
|
|
183
184
|
```ts
|
|
184
185
|
// src/app.ts
|
|
185
186
|
import { createNanoApp } from '@nano-app/backend';
|
|
186
|
-
import { authOptions } from './auth';
|
|
187
187
|
|
|
188
|
-
createNanoApp({
|
|
189
|
-
packages: { betterAuth: authOptions },
|
|
190
|
-
mainPlugin: { database: { enabled: true } },
|
|
191
|
-
});
|
|
188
|
+
createNanoApp({ mainPlugin: { database: { enabled: true } } });
|
|
192
189
|
```
|
|
193
190
|
|
|
194
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:
|
|
195
192
|
|
|
196
193
|
```bash
|
|
197
|
-
npx auth generate --
|
|
194
|
+
npx auth generate --adapter prisma --dialect postgresql --output src/prisma/schema.prisma
|
|
198
195
|
npx prisma migrate dev
|
|
199
196
|
```
|
|
200
197
|
|
|
201
|
-
- 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`.
|
|
202
199
|
- Startup also fails if your schema lacks a model the options need (`user`, `session`, ...), naming them.
|
|
203
200
|
- Its endpoints are mounted with your routes, at its `basePath` (default `/api/auth`), and stay public.
|
|
204
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).
|
|
@@ -212,6 +209,8 @@ npx prisma migrate dev
|
|
|
212
209
|
- `createRoute({ public: true, ... })` answers without a session; its handlers still receive `session` when the request has one. An unknown path answers 404.
|
|
213
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.
|
|
214
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`.
|
|
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.
|
|
215
214
|
|
|
216
215
|
### Permissions
|
|
217
216
|
|
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);
|