nuxt-auto-crud 1.12.1 → 1.14.1
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 +9 -7
- package/dist/module.json +1 -1
- package/dist/module.mjs +13 -43
- package/dist/runtime/server/utils/auth.js +24 -28
- package/package.json +2 -1
- package/src/runtime/server/utils/auth.ts +33 -35
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> **Note:** This module is currently in its alpha stage. However, you can use it to accelerate MVP development. It has not been tested thoroughly enough for production use; only happy-path testing is performed for each release.
|
|
4
4
|
|
|
5
|
-
Auto-
|
|
5
|
+
Auto-expose RESTful CRUD APIs for your **Nuxt** application based solely on your database schema. Minimal configuration required.
|
|
6
6
|
|
|
7
7
|
**Core Philosophy:**
|
|
8
8
|
The main objective of this module is to **expose CRUD APIs without the need for writing code**. You define your database schema, and `nuxt-auto-crud` handles the rest.
|
|
@@ -44,7 +44,7 @@ bun run dev
|
|
|
44
44
|
1. **Fullstack App**: The template includes the `nuxt-auto-crud` module, providing both the backend APIs and the frontend UI. [Watch Demo](https://youtu.be/M9-koXmhB9k)
|
|
45
45
|
2. **Frontend Only**: You can use the template just for the frontend. In this case, you don't need to install the module in the frontend app. Instead, you would install `nuxt-auto-crud` in a separate backend setup (e.g., another Nuxt project acting as the API).
|
|
46
46
|
|
|
47
|
-
Detailed instructions can be found in [https://auto-crud.clifland.in/](https://auto-crud.clifland.in/)
|
|
47
|
+
Detailed instructions can be found in [https://auto-crud.clifland.in/docs](https://auto-crud.clifland.in/docs)
|
|
48
48
|
|
|
49
49
|
### 2. Manual Setup (Existing Project)
|
|
50
50
|
|
|
@@ -207,7 +207,7 @@ The new API endpoints (e.g., `/api/posts`) will be automatically available. [Wat
|
|
|
207
207
|
|
|
208
208
|
### 3. Backend-only App (API Mode)
|
|
209
209
|
|
|
210
|
-
If you are using Nuxt as a backend for a separate client application (e.g., mobile app, SPA), you can use this module to quickly
|
|
210
|
+
If you are using Nuxt as a backend for a separate client application (e.g., mobile app, SPA), you can use this module to quickly expose REST APIs.
|
|
211
211
|
|
|
212
212
|
In this case, you might handle authentication differently (e.g., validating tokens in middleware) or disable the built-in auth checks if you have a global auth middleware.
|
|
213
213
|
|
|
@@ -467,12 +467,14 @@ You can customize hidden fields by modifying the `modelMapper.ts` utility.
|
|
|
467
467
|
## 🔗 Other Helpful Links
|
|
468
468
|
|
|
469
469
|
- **Template:** [https://github.com/clifordpereira/nuxt-auto-crud_template](https://github.com/clifordpereira/nuxt-auto-crud_template)
|
|
470
|
-
- **Docs:** [https://auto-crud.clifland.in/](https://auto-crud.clifland.in/)
|
|
470
|
+
- **Docs:** [https://auto-crud.clifland.in/docs](https://auto-crud.clifland.in/docs)
|
|
471
471
|
- **Repo:** [https://github.com/clifordpereira/nuxt-auto-crud](https://github.com/clifordpereira/nuxt-auto-crud)
|
|
472
|
-
- **YouTube:** [https://youtu.be/M9-koXmhB9k](https://youtu.be/M9-koXmhB9k)
|
|
473
|
-
- **YouTube
|
|
472
|
+
- **YouTube (Installation):** [https://youtu.be/M9-koXmhB9k](https://youtu.be/M9-koXmhB9k)
|
|
473
|
+
- **YouTube (Add Schemas):** [https://youtu.be/7gW0KW1KtN0](https://youtu.be/7gW0KW1KtN0)
|
|
474
|
+
- **YouTube (Various Permissions):** [https://www.youtube.com/watch?v=Yty3OCYbwOo](https://www.youtube.com/watch?v=Yty3OCYbwOo)
|
|
474
475
|
- **npm:** [https://www.npmjs.com/package/nuxt-auto-crud](https://www.npmjs.com/package/nuxt-auto-crud)
|
|
475
|
-
- **
|
|
476
|
+
- **Github Discussions:** [https://github.com/clifordpereira/nuxt-auto-crud/discussions/1](https://github.com/clifordpereira/nuxt-auto-crud/discussions/1)
|
|
477
|
+
- **Discord:** [https://discord.gg/hGgyEaGu](https://discord.gg/hGgyEaGu)
|
|
476
478
|
|
|
477
479
|
## 🤝 Contributing
|
|
478
480
|
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineNuxtModule, createResolver, addServerHandler, addServerImportsDir
|
|
1
|
+
import { defineNuxtModule, createResolver, addImportsDir, addServerHandler, addServerImportsDir } from '@nuxt/kit';
|
|
2
2
|
|
|
3
3
|
const module$1 = defineNuxtModule({
|
|
4
4
|
meta: {
|
|
@@ -22,52 +22,19 @@ const module$1 = defineNuxtModule({
|
|
|
22
22
|
options.drizzlePath
|
|
23
23
|
);
|
|
24
24
|
nuxt.options.alias["#site/drizzle"] = drizzlePath;
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
"server/utils/ability"
|
|
28
|
-
);
|
|
29
|
-
nuxt.options.alias["#site/ability"] = abilityPath;
|
|
30
|
-
nuxt.options.alias["#authorization"] = nuxt.options.alias["#authorization"] || "nuxt-authorization/utils";
|
|
25
|
+
addImportsDir(resolver.resolve(nuxt.options.rootDir, "shared/utils"));
|
|
26
|
+
nuxt.options.alias["#authorization"] ||= "nuxt-authorization/utils";
|
|
31
27
|
const { loadConfig } = await import('c12');
|
|
32
28
|
const { config: externalConfig } = await loadConfig({
|
|
33
29
|
name: "autocrud",
|
|
34
30
|
cwd: nuxt.options.rootDir
|
|
35
31
|
});
|
|
36
|
-
|
|
37
|
-
authentication:
|
|
38
|
-
authorization:
|
|
39
|
-
type: "session"
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
mergedAuth = {
|
|
43
|
-
authentication: true,
|
|
44
|
-
authorization: true,
|
|
45
|
-
type: "session",
|
|
46
|
-
...typeof externalConfig?.auth === "object" ? externalConfig.auth : {}
|
|
47
|
-
};
|
|
48
|
-
} else if (options.auth === false) {
|
|
49
|
-
mergedAuth = {
|
|
50
|
-
authentication: false,
|
|
51
|
-
authorization: false,
|
|
52
|
-
type: "session"
|
|
53
|
-
};
|
|
54
|
-
} else {
|
|
55
|
-
mergedAuth = {
|
|
56
|
-
authentication: true,
|
|
57
|
-
// Default to true if object provided? Or undefined?
|
|
58
|
-
// If options.auth is undefined, we might want defaults.
|
|
59
|
-
// But if defaults say auth: false, then options.auth might be undefined.
|
|
60
|
-
// Let's stick to the plan: default is false.
|
|
61
|
-
...typeof externalConfig?.auth === "object" ? externalConfig.auth : {},
|
|
62
|
-
...typeof options.auth === "object" ? options.auth : {}
|
|
63
|
-
};
|
|
64
|
-
if (mergedAuth.authentication === void 0) {
|
|
65
|
-
mergedAuth.authentication = false;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
const mergedResources = {
|
|
69
|
-
...externalConfig?.resources,
|
|
70
|
-
...options.resources
|
|
32
|
+
const mergedAuth = options.auth === false ? { authentication: false, authorization: false, type: "session" } : {
|
|
33
|
+
authentication: true,
|
|
34
|
+
authorization: options.auth === true,
|
|
35
|
+
type: "session",
|
|
36
|
+
...typeof externalConfig?.auth === "object" ? externalConfig.auth : {},
|
|
37
|
+
...typeof options.auth === "object" ? options.auth : {}
|
|
71
38
|
};
|
|
72
39
|
nuxt.options.runtimeConfig.autoCrud = {
|
|
73
40
|
auth: {
|
|
@@ -76,7 +43,10 @@ const module$1 = defineNuxtModule({
|
|
|
76
43
|
type: mergedAuth.type ?? "session",
|
|
77
44
|
jwtSecret: mergedAuth.jwtSecret
|
|
78
45
|
},
|
|
79
|
-
resources:
|
|
46
|
+
resources: {
|
|
47
|
+
...externalConfig?.resources,
|
|
48
|
+
...options.resources
|
|
49
|
+
}
|
|
80
50
|
};
|
|
81
51
|
const apiDir = resolver.resolve("./runtime/server/api");
|
|
82
52
|
addServerHandler({
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { createError } from "h3";
|
|
2
|
-
import globalAbility from "#
|
|
3
|
-
import { requireUserSession, allows } from "#imports";
|
|
2
|
+
import { requireUserSession, allows, getUserSession, abilities as globalAbility } from "#imports";
|
|
4
3
|
import { useAutoCrudConfig } from "./config.js";
|
|
5
4
|
import { verifyJwtToken } from "./jwt.js";
|
|
6
5
|
export async function checkAdminAccess(event, model, action) {
|
|
@@ -15,42 +14,39 @@ export async function checkAdminAccess(event, model, action) {
|
|
|
15
14
|
}
|
|
16
15
|
return verifyJwtToken(event, auth.jwtSecret);
|
|
17
16
|
}
|
|
17
|
+
let user = null;
|
|
18
18
|
try {
|
|
19
|
-
await
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
const session = await getUserSession(event);
|
|
20
|
+
user = session.user;
|
|
21
|
+
} catch {
|
|
22
|
+
}
|
|
23
|
+
if (auth.authorization) {
|
|
24
|
+
try {
|
|
25
|
+
const guestCheck = !user && (typeof globalAbility === "function" ? globalAbility : null);
|
|
26
|
+
const allowed = guestCheck ? await guestCheck(null, model, action) : await allows(event, globalAbility, model, action);
|
|
22
27
|
if (!allowed) {
|
|
23
|
-
throw createError({
|
|
24
|
-
|
|
25
|
-
message: "Forbidden"
|
|
26
|
-
});
|
|
28
|
+
if (user) throw createError({ statusCode: 403, message: "Forbidden" });
|
|
29
|
+
return false;
|
|
27
30
|
}
|
|
31
|
+
return true;
|
|
32
|
+
} catch (err) {
|
|
33
|
+
if (err.statusCode === 403) throw err;
|
|
34
|
+
return false;
|
|
28
35
|
}
|
|
36
|
+
}
|
|
37
|
+
if (user) {
|
|
29
38
|
return true;
|
|
30
|
-
} catch (e) {
|
|
31
|
-
if (e.statusCode === 403) {
|
|
32
|
-
throw e;
|
|
33
|
-
}
|
|
34
|
-
return false;
|
|
35
39
|
}
|
|
40
|
+
return false;
|
|
36
41
|
}
|
|
37
42
|
export async function ensureAuthenticated(event) {
|
|
38
43
|
const { auth } = useAutoCrudConfig();
|
|
39
|
-
if (!auth?.authentication)
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
let isAuthenticated = false;
|
|
44
|
+
if (!auth?.authentication) return;
|
|
43
45
|
if (auth.type === "jwt" && auth.jwtSecret) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
try {
|
|
47
|
-
await requireUserSession(event);
|
|
48
|
-
isAuthenticated = true;
|
|
49
|
-
} catch {
|
|
50
|
-
isAuthenticated = false;
|
|
46
|
+
if (!await verifyJwtToken(event, auth.jwtSecret)) {
|
|
47
|
+
throw createError({ statusCode: 401, message: "Unauthorized" });
|
|
51
48
|
}
|
|
49
|
+
return;
|
|
52
50
|
}
|
|
53
|
-
|
|
54
|
-
throw createError({ statusCode: 401, message: "Unauthorized" });
|
|
55
|
-
}
|
|
51
|
+
await requireUserSession(event);
|
|
56
52
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuxt-auto-crud",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.14.1",
|
|
4
4
|
"description": "Exposes RESTful CRUD APIs for your Nuxt app based solely on your database migrations.",
|
|
5
5
|
"author": "Cliford Pereira",
|
|
6
6
|
"license": "MIT",
|
|
@@ -61,6 +61,7 @@
|
|
|
61
61
|
"@iconify-json/heroicons": "^1.2.3",
|
|
62
62
|
"@iconify-json/lucide": "^1.2.77",
|
|
63
63
|
"@iconify-json/simple-icons": "^1.2.61",
|
|
64
|
+
"@iconify-json/vscode-icons": "^1.2.37",
|
|
64
65
|
"@nuxt/devtools": "^3.1.0",
|
|
65
66
|
"@nuxt/eslint-config": "^1.10.0",
|
|
66
67
|
"@nuxt/module-builder": "^1.0.2",
|
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
/// <reference path="../../auth.d.ts" />
|
|
3
3
|
import type { H3Event } from 'h3'
|
|
4
4
|
import { createError } from 'h3'
|
|
5
|
-
import globalAbility from '#site/ability'
|
|
6
5
|
// @ts-expect-error - #imports is available in runtime
|
|
7
|
-
import { requireUserSession, allows } from '#imports'
|
|
6
|
+
import { requireUserSession, allows, getUserSession, abilities as globalAbility } from '#imports'
|
|
8
7
|
import { useAutoCrudConfig } from './config'
|
|
9
8
|
import { verifyJwtToken } from './jwt'
|
|
10
9
|
|
|
@@ -24,55 +23,54 @@ export async function checkAdminAccess(event: H3Event, model: string, action: st
|
|
|
24
23
|
}
|
|
25
24
|
|
|
26
25
|
// Session based (default)
|
|
26
|
+
let user = null
|
|
27
27
|
try {
|
|
28
|
-
await
|
|
28
|
+
const session = await getUserSession(event)
|
|
29
|
+
user = session.user
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// No session or error fetching session
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Check authorization if enabled
|
|
36
|
+
if (auth.authorization) {
|
|
37
|
+
try {
|
|
38
|
+
const guestCheck = !user && (typeof globalAbility === 'function' ? globalAbility : null)
|
|
39
|
+
const allowed = guestCheck
|
|
40
|
+
? await guestCheck(null, model, action)
|
|
41
|
+
: await allows(event, globalAbility, model, action)
|
|
29
42
|
|
|
30
|
-
// Check authorization if enabled
|
|
31
|
-
if (auth.authorization) {
|
|
32
|
-
const allowed = await allows(event, globalAbility, model, action)
|
|
33
43
|
if (!allowed) {
|
|
34
|
-
throw createError({
|
|
35
|
-
|
|
36
|
-
message: 'Forbidden',
|
|
37
|
-
})
|
|
44
|
+
if (user) throw createError({ statusCode: 403, message: 'Forbidden' })
|
|
45
|
+
return false
|
|
38
46
|
}
|
|
47
|
+
return true
|
|
39
48
|
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
if ((err as { statusCode: number }).statusCode === 403) throw err
|
|
51
|
+
return false
|
|
52
|
+
}
|
|
53
|
+
}
|
|
40
54
|
|
|
55
|
+
// If authorization is NOT enabled, we rely on authentication only.
|
|
56
|
+
if (user) {
|
|
41
57
|
return true
|
|
42
58
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
46
|
-
if ((e as any).statusCode === 403) {
|
|
47
|
-
throw e
|
|
48
|
-
}
|
|
49
|
-
// Otherwise (401 from requireUserSession or function not available), return false (treat as guest)
|
|
50
|
-
return false
|
|
51
|
-
}
|
|
59
|
+
|
|
60
|
+
return false
|
|
52
61
|
}
|
|
53
62
|
|
|
54
63
|
export async function ensureAuthenticated(event: H3Event): Promise<void> {
|
|
55
64
|
const { auth } = useAutoCrudConfig()
|
|
56
65
|
|
|
57
|
-
if (!auth?.authentication)
|
|
58
|
-
return
|
|
59
|
-
}
|
|
66
|
+
if (!auth?.authentication) return
|
|
60
67
|
|
|
61
|
-
let isAuthenticated = false
|
|
62
68
|
if (auth.type === 'jwt' && auth.jwtSecret) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
else {
|
|
66
|
-
try {
|
|
67
|
-
await requireUserSession(event)
|
|
68
|
-
isAuthenticated = true
|
|
69
|
-
}
|
|
70
|
-
catch {
|
|
71
|
-
isAuthenticated = false
|
|
69
|
+
if (!await verifyJwtToken(event, auth.jwtSecret)) {
|
|
70
|
+
throw createError({ statusCode: 401, message: 'Unauthorized' })
|
|
72
71
|
}
|
|
72
|
+
return
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
|
|
76
|
-
throw createError({ statusCode: 401, message: 'Unauthorized' })
|
|
77
|
-
}
|
|
75
|
+
await requireUserSession(event)
|
|
78
76
|
}
|