nuxt-auto-crud 1.24.0 → 1.26.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 +30 -360
- package/dist/module.json +1 -1
- package/dist/module.mjs +5 -0
- package/dist/runtime/server/api/[model]/[id].patch.js +3 -2
- package/dist/runtime/server/api/[model]/index.post.js +3 -2
- package/dist/runtime/server/api/_meta.get.d.ts +21 -0
- package/dist/runtime/server/api/_meta.get.js +91 -0
- package/dist/runtime/server/api/_schema/[table].get.d.ts +1 -0
- package/dist/runtime/server/utils/auth.js +19 -6
- package/dist/runtime/server/utils/constants.d.ts +2 -0
- package/dist/runtime/server/utils/constants.js +36 -0
- package/dist/runtime/server/utils/modelMapper.d.ts +7 -65
- package/dist/runtime/server/utils/modelMapper.js +27 -15
- package/dist/runtime/server/utils/schema.d.ts +2 -0
- package/dist/runtime/server/utils/schema.js +4 -0
- package/package.json +3 -2
- package/src/runtime/server/api/[model]/[id].patch.ts +3 -2
- package/src/runtime/server/api/[model]/index.post.ts +5 -4
- package/src/runtime/server/api/_meta.get.ts +111 -0
- package/src/runtime/server/utils/auth.ts +29 -4
- package/src/runtime/server/utils/constants.ts +25 -0
- package/src/runtime/server/utils/modelMapper.ts +45 -111
- package/src/runtime/server/utils/schema.ts +8 -0
|
@@ -1,86 +1,28 @@
|
|
|
1
1
|
import type { SQLiteTable } from 'drizzle-orm/sqlite-core';
|
|
2
|
-
|
|
3
|
-
* Custom updatable fields configuration (optional)
|
|
4
|
-
* Only define here if you want to override the auto-detection
|
|
5
|
-
*
|
|
6
|
-
* Example:
|
|
7
|
-
* export const customUpdatableFields: Record<string, string[]> = {
|
|
8
|
-
* users: ['name', 'avatar'], // Only these fields can be updated
|
|
9
|
-
* }
|
|
10
|
-
*/
|
|
2
|
+
import type { z } from 'zod';
|
|
11
3
|
export declare const customUpdatableFields: Record<string, string[]>;
|
|
12
|
-
/**
|
|
13
|
-
* Custom hidden fields configuration (optional)
|
|
14
|
-
* Only define here if you want to override the default hidden fields
|
|
15
|
-
*/
|
|
16
4
|
export declare const customHiddenFields: Record<string, string[]>;
|
|
17
|
-
/**
|
|
18
|
-
* Auto-generated model table map
|
|
19
|
-
* Automatically includes all tables from schema
|
|
20
|
-
*/
|
|
21
5
|
export declare const modelTableMap: Record<string, unknown>;
|
|
22
6
|
/**
|
|
23
|
-
*
|
|
24
|
-
* @param modelName - The name of the model (e.g., 'users', 'products')
|
|
25
|
-
* @returns The corresponding database table
|
|
26
|
-
* @throws Error if model is not found
|
|
7
|
+
* @throws 404 if modelName is not found in tableMap.
|
|
27
8
|
*/
|
|
28
9
|
export declare function getTableForModel(modelName: string): SQLiteTable;
|
|
29
|
-
/**
|
|
30
|
-
* Gets the updatable fields for a model
|
|
31
|
-
* @param modelName - The name of the model
|
|
32
|
-
* @returns Array of field names that can be updated
|
|
33
|
-
*/
|
|
34
10
|
export declare function getUpdatableFields(modelName: string): string[];
|
|
35
11
|
/**
|
|
36
|
-
* Filters
|
|
37
|
-
* @param modelName - The name of the model
|
|
38
|
-
* @param data - The data object to filter
|
|
39
|
-
* @returns Filtered object with only updatable fields
|
|
12
|
+
* Filters and coerces data for updates, handling timestamp conversion.
|
|
40
13
|
*/
|
|
41
14
|
export declare function filterUpdatableFields(modelName: string, data: Record<string, unknown>): Record<string, unknown>;
|
|
42
|
-
/**
|
|
43
|
-
* Gets the singular name for a model (for error messages)
|
|
44
|
-
* Uses pluralize library for accurate singular/plural conversion
|
|
45
|
-
* @param modelName - The plural model name
|
|
46
|
-
* @returns The singular name in PascalCase
|
|
47
|
-
*/
|
|
48
15
|
export declare function getModelSingularName(modelName: string): string;
|
|
49
|
-
/**
|
|
50
|
-
* Gets the plural name for a model
|
|
51
|
-
* @param modelName - The model name (singular or plural)
|
|
52
|
-
* @returns The plural name
|
|
53
|
-
* @return The plural name
|
|
54
|
-
*/
|
|
55
16
|
export declare function getModelPluralName(modelName: string): string;
|
|
56
|
-
/**
|
|
57
|
-
* Lists all available models
|
|
58
|
-
* @returns Array of model names
|
|
59
|
-
*/
|
|
60
17
|
export declare function getAvailableModels(): string[];
|
|
61
|
-
/**
|
|
62
|
-
* Gets the hidden fields for a model
|
|
63
|
-
* @param modelName - The name of the model
|
|
64
|
-
* @returns Array of field names that should be hidden
|
|
65
|
-
*/
|
|
66
18
|
export declare function getHiddenFields(modelName: string): string[];
|
|
67
|
-
/**
|
|
68
|
-
* Gets the public columns for a model
|
|
69
|
-
* @param modelName - The name of the model
|
|
70
|
-
* @returns Array of field names that are public (or undefined if all are public)
|
|
71
|
-
*/
|
|
72
19
|
export declare function getPublicColumns(modelName: string): string[] | undefined;
|
|
73
20
|
/**
|
|
74
|
-
*
|
|
75
|
-
* @param modelName - The name of the model
|
|
76
|
-
* @param data - The data object to filter
|
|
77
|
-
* @returns Filtered object
|
|
21
|
+
* Restricts payload to runtimeConfig resource whitelist and filters hidden fields.
|
|
78
22
|
*/
|
|
79
23
|
export declare function filterPublicColumns(modelName: string, data: Record<string, unknown>): Record<string, unknown>;
|
|
24
|
+
export declare function filterHiddenFields(modelName: string, data: Record<string, unknown>): Record<string, unknown>;
|
|
80
25
|
/**
|
|
81
|
-
*
|
|
82
|
-
* @param modelName - The name of the model
|
|
83
|
-
* @param data - The data object to filter
|
|
84
|
-
* @returns Filtered object without hidden fields
|
|
26
|
+
* Generates Zod schema via drizzle-zod, omitting server-managed and protected fields.
|
|
85
27
|
*/
|
|
86
|
-
export declare function
|
|
28
|
+
export declare function getZodSchema(modelName: string, type?: 'insert' | 'patch'): z.ZodObject<any, any>;
|
|
@@ -4,15 +4,10 @@ import { pascalCase } from "scule";
|
|
|
4
4
|
import { getTableColumns as getDrizzleTableColumns, getTableName } from "drizzle-orm";
|
|
5
5
|
import { createError } from "h3";
|
|
6
6
|
import { useRuntimeConfig } from "#imports";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
export const customUpdatableFields = {
|
|
10
|
-
|
|
11
|
-
// By default, all fields except PROTECTED_FIELDS are updatable
|
|
12
|
-
};
|
|
13
|
-
export const customHiddenFields = {
|
|
14
|
-
// Add custom hidden fields here if needed
|
|
15
|
-
};
|
|
7
|
+
import { createInsertSchema } from "drizzle-zod";
|
|
8
|
+
import { PROTECTED_FIELDS, HIDDEN_FIELDS } from "./constants.js";
|
|
9
|
+
export const customUpdatableFields = {};
|
|
10
|
+
export const customHiddenFields = {};
|
|
16
11
|
function buildModelTableMap() {
|
|
17
12
|
const tableMap = {};
|
|
18
13
|
for (const [key, value] of Object.entries(schema)) {
|
|
@@ -56,7 +51,7 @@ export function getUpdatableFields(modelName) {
|
|
|
56
51
|
const table = modelTableMap[modelName];
|
|
57
52
|
if (!table) return [];
|
|
58
53
|
const allColumns = getTableColumns(table);
|
|
59
|
-
return allColumns.filter((col) => !PROTECTED_FIELDS.includes(col));
|
|
54
|
+
return allColumns.filter((col) => !PROTECTED_FIELDS.includes(col) && !HIDDEN_FIELDS.includes(col));
|
|
60
55
|
}
|
|
61
56
|
export function filterUpdatableFields(modelName, data) {
|
|
62
57
|
const allowedFields = getUpdatableFields(modelName);
|
|
@@ -86,10 +81,7 @@ export function getAvailableModels() {
|
|
|
86
81
|
return Object.keys(modelTableMap);
|
|
87
82
|
}
|
|
88
83
|
export function getHiddenFields(modelName) {
|
|
89
|
-
|
|
90
|
-
return customHiddenFields[modelName];
|
|
91
|
-
}
|
|
92
|
-
return HIDDEN_FIELDS;
|
|
84
|
+
return customHiddenFields[modelName] ?? HIDDEN_FIELDS;
|
|
93
85
|
}
|
|
94
86
|
export function getPublicColumns(modelName) {
|
|
95
87
|
const { resources } = useRuntimeConfig().autoCrud;
|
|
@@ -101,8 +93,9 @@ export function filterPublicColumns(modelName, data) {
|
|
|
101
93
|
return filterHiddenFields(modelName, data);
|
|
102
94
|
}
|
|
103
95
|
const filtered = {};
|
|
96
|
+
const hidden = getHiddenFields(modelName);
|
|
104
97
|
for (const [key, value] of Object.entries(data)) {
|
|
105
|
-
if (publicColumns.includes(key) && !
|
|
98
|
+
if (publicColumns.includes(key) && !hidden.includes(key)) {
|
|
106
99
|
filtered[key] = value;
|
|
107
100
|
}
|
|
108
101
|
}
|
|
@@ -118,3 +111,22 @@ export function filterHiddenFields(modelName, data) {
|
|
|
118
111
|
}
|
|
119
112
|
return filtered;
|
|
120
113
|
}
|
|
114
|
+
export function getZodSchema(modelName, type = "insert") {
|
|
115
|
+
const table = getTableForModel(modelName);
|
|
116
|
+
const schema2 = createInsertSchema(table);
|
|
117
|
+
if (type === "patch") {
|
|
118
|
+
return schema2.partial();
|
|
119
|
+
}
|
|
120
|
+
const OMIT_ON_CREATE = [
|
|
121
|
+
...PROTECTED_FIELDS,
|
|
122
|
+
...HIDDEN_FIELDS
|
|
123
|
+
];
|
|
124
|
+
const columns = getDrizzleTableColumns(table);
|
|
125
|
+
const fieldsToOmit = {};
|
|
126
|
+
OMIT_ON_CREATE.forEach((field) => {
|
|
127
|
+
if (columns[field]) {
|
|
128
|
+
fieldsToOmit[field] = true;
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
return schema2.omit(fieldsToOmit);
|
|
132
|
+
}
|
|
@@ -7,11 +7,13 @@ export interface Field {
|
|
|
7
7
|
}
|
|
8
8
|
export declare function drizzleTableToFields(table: any, resourceName: string): {
|
|
9
9
|
resource: string;
|
|
10
|
+
labelField: string;
|
|
10
11
|
fields: Field[];
|
|
11
12
|
};
|
|
12
13
|
export declare function getRelations(): Promise<Record<string, Record<string, string>>>;
|
|
13
14
|
export declare function getAllSchemas(): Promise<Record<string, any>>;
|
|
14
15
|
export declare function getSchema(tableName: string): Promise<{
|
|
15
16
|
resource: string;
|
|
17
|
+
labelField: string;
|
|
16
18
|
fields: Field[];
|
|
17
19
|
} | undefined>;
|
|
@@ -15,6 +15,8 @@ export function drizzleTableToFields(table, resourceName) {
|
|
|
15
15
|
selectOptions
|
|
16
16
|
});
|
|
17
17
|
}
|
|
18
|
+
const fieldNames = fields.map((f) => f.name);
|
|
19
|
+
const labelField = fieldNames.find((n) => n === "name") || fieldNames.find((n) => n === "title") || fieldNames.find((n) => n === "email") || "id";
|
|
18
20
|
try {
|
|
19
21
|
const config = getTableConfig(table);
|
|
20
22
|
config.foreignKeys.forEach((fk) => {
|
|
@@ -32,6 +34,8 @@ export function drizzleTableToFields(table, resourceName) {
|
|
|
32
34
|
}
|
|
33
35
|
return {
|
|
34
36
|
resource: resourceName,
|
|
37
|
+
labelField,
|
|
38
|
+
// metadata point
|
|
35
39
|
fields
|
|
36
40
|
};
|
|
37
41
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuxt-auto-crud",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.26.0",
|
|
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",
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
"@nuxt/scripts": "^0.13.0",
|
|
52
52
|
"@types/pluralize": "^0.0.33",
|
|
53
53
|
"c12": "^2.0.1",
|
|
54
|
+
"drizzle-zod": "^0.8.3",
|
|
54
55
|
"jose": "^5.9.6",
|
|
55
56
|
"pluralize": "^8.0.0",
|
|
56
57
|
"scule": "^1.0.0"
|
|
@@ -78,7 +79,7 @@
|
|
|
78
79
|
"drizzle-orm": "^0.38.3",
|
|
79
80
|
"eslint": "^9.39.1",
|
|
80
81
|
"nuxt": "^4.2.1",
|
|
81
|
-
"nuxt-auth-utils": "^0.5.
|
|
82
|
+
"nuxt-auth-utils": "^0.5.27",
|
|
82
83
|
"nuxt-authorization": "^0.3.5",
|
|
83
84
|
"typescript": "~5.9.3",
|
|
84
85
|
"vitest": "^4.0.13",
|
|
@@ -4,7 +4,7 @@ import type { H3Event } from 'h3'
|
|
|
4
4
|
// @ts-expect-error - #imports is a virtual alias
|
|
5
5
|
import { getUserSession } from '#imports'
|
|
6
6
|
import { eq } from 'drizzle-orm'
|
|
7
|
-
import { getTableForModel,
|
|
7
|
+
import { getTableForModel, getZodSchema } from '../../utils/modelMapper'
|
|
8
8
|
import type { TableWithId } from '../../types'
|
|
9
9
|
|
|
10
10
|
// @ts-expect-error - hub:db is a virtual alias
|
|
@@ -20,7 +20,8 @@ export default eventHandler(async (event) => {
|
|
|
20
20
|
const table = getTableForModel(model) as TableWithId
|
|
21
21
|
|
|
22
22
|
const body = await readBody(event)
|
|
23
|
-
const
|
|
23
|
+
const schema = getZodSchema(model, 'patch')
|
|
24
|
+
const payload = await schema.parseAsync(body)
|
|
24
25
|
|
|
25
26
|
// Custom check for status update permission
|
|
26
27
|
if ('status' in payload) {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// server/api/[model]/index.post.ts
|
|
2
2
|
import { eventHandler, getRouterParams, readBody } from 'h3'
|
|
3
3
|
import type { H3Event } from 'h3'
|
|
4
|
-
// @ts-expect-error - #imports is a virtual alias
|
|
4
|
+
// @ts-expect-error - '#imports' is a virtual alias
|
|
5
5
|
import { getUserSession } from '#imports'
|
|
6
|
-
import { getTableForModel,
|
|
7
|
-
// @ts-expect-error - hub:db is a virtual alias
|
|
6
|
+
import { getTableForModel, getZodSchema } from '../../utils/modelMapper'
|
|
7
|
+
// @ts-expect-error - 'hub:db' is a virtual alias
|
|
8
8
|
import { db } from 'hub:db'
|
|
9
9
|
import { ensureResourceAccess, formatResourceResult, hashPayloadFields } from '../../utils/handler'
|
|
10
10
|
|
|
@@ -15,7 +15,8 @@ export default eventHandler(async (event) => {
|
|
|
15
15
|
const table = getTableForModel(model)
|
|
16
16
|
|
|
17
17
|
const body = await readBody(event)
|
|
18
|
-
const
|
|
18
|
+
const schema = getZodSchema(model, 'insert')
|
|
19
|
+
const payload = await schema.parseAsync(body)
|
|
19
20
|
|
|
20
21
|
// Custom check for status update permission (or just remove it during creation as per requirement)
|
|
21
22
|
if ('status' in payload) {
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { eventHandler, getQuery, getHeader } from 'h3'
|
|
2
|
+
import { getTableForModel, getAvailableModels } from '../utils/modelMapper'
|
|
3
|
+
import { getTableColumns as getDrizzleTableColumns } from 'drizzle-orm'
|
|
4
|
+
import { getTableConfig } from 'drizzle-orm/sqlite-core'
|
|
5
|
+
import { PROTECTED_FIELDS, HIDDEN_FIELDS } from '../utils/constants'
|
|
6
|
+
// @ts-expect-error - 'hub:db' is a virtual alias
|
|
7
|
+
import { db } from 'hub:db'
|
|
8
|
+
import { ensureAuthenticated } from '../utils/auth'
|
|
9
|
+
|
|
10
|
+
export default eventHandler(async (event) => {
|
|
11
|
+
await ensureAuthenticated(event)
|
|
12
|
+
|
|
13
|
+
const query = getQuery(event)
|
|
14
|
+
const acceptHeader = getHeader(event, 'accept') || ''
|
|
15
|
+
|
|
16
|
+
const models = getAvailableModels().length > 0
|
|
17
|
+
? getAvailableModels()
|
|
18
|
+
: Object.keys(db?.query || {})
|
|
19
|
+
|
|
20
|
+
const resources = models.map((model) => {
|
|
21
|
+
try {
|
|
22
|
+
const table = getTableForModel(model)
|
|
23
|
+
const columns = getDrizzleTableColumns(table)
|
|
24
|
+
const config = getTableConfig(table)
|
|
25
|
+
|
|
26
|
+
const fields = Object.entries(columns)
|
|
27
|
+
.filter(([name]) => !HIDDEN_FIELDS.includes(name))
|
|
28
|
+
.map(([name, col]) => {
|
|
29
|
+
let references = null
|
|
30
|
+
// @ts-expect-error - Drizzle foreign key internals
|
|
31
|
+
const fk = config?.foreignKeys.find(f => f.reference().columns[0].name === col.name)
|
|
32
|
+
|
|
33
|
+
if (fk) {
|
|
34
|
+
// @ts-expect-error - Drizzle internals
|
|
35
|
+
references = fk.reference().foreignTable[Symbol.for('drizzle:Name')]
|
|
36
|
+
}
|
|
37
|
+
// @ts-expect-error - Drizzle internal referenceConfig
|
|
38
|
+
else if (col.referenceConfig?.foreignTable) {
|
|
39
|
+
// @ts-expect-error - Drizzle internal referenceConfig
|
|
40
|
+
const foreignTable = col.referenceConfig.foreignTable
|
|
41
|
+
references = foreignTable[Symbol.for('drizzle:Name')] || foreignTable.name
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const semanticType = col.columnType.toLowerCase().replace('sqlite', '')
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
name,
|
|
48
|
+
type: semanticType,
|
|
49
|
+
required: col.notNull || false,
|
|
50
|
+
isEnum: !!col.enumValues,
|
|
51
|
+
options: col.enumValues || null,
|
|
52
|
+
references,
|
|
53
|
+
isRelation: !!references,
|
|
54
|
+
// Agentic Hint: Is this field writable by the user/agent?
|
|
55
|
+
isReadOnly: PROTECTED_FIELDS.includes(name),
|
|
56
|
+
}
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
const fieldNames = fields.map(f => f.name)
|
|
60
|
+
const labelField = fieldNames.find(n => n === 'name')
|
|
61
|
+
|| fieldNames.find(n => n === 'title')
|
|
62
|
+
|| fieldNames.find(n => n === 'email')
|
|
63
|
+
|| 'id'
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
resource: model,
|
|
67
|
+
endpoint: `/api/${model}`,
|
|
68
|
+
labelField,
|
|
69
|
+
methods: ['GET', 'POST', 'PATCH', 'DELETE'],
|
|
70
|
+
fields,
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return null
|
|
75
|
+
}
|
|
76
|
+
}).filter(Boolean)
|
|
77
|
+
|
|
78
|
+
const payload = {
|
|
79
|
+
architecture: 'Clifland-NAC',
|
|
80
|
+
version: '1.0.0-agentic',
|
|
81
|
+
resources,
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const currentToken = getQuery(event).token || (getHeader(event, 'authorization')?.split(' ')[1])
|
|
85
|
+
const tokenSuffix = currentToken ? `?token=${currentToken}` : ''
|
|
86
|
+
|
|
87
|
+
// --- CONTENT NEGOTIATION FOR AGENTIC TOOLS ---
|
|
88
|
+
if (query.format === 'md' || acceptHeader.includes('text/markdown')) {
|
|
89
|
+
let markdown = `# ${payload.architecture} API Manifest (v${payload.version})\n\n`
|
|
90
|
+
|
|
91
|
+
payload.resources.forEach((res) => {
|
|
92
|
+
if (!res) return
|
|
93
|
+
markdown += `### Resource: ${res.resource}\n`
|
|
94
|
+
markdown += `- **Endpoint**: \`${res.endpoint}${tokenSuffix}\`\n`
|
|
95
|
+
markdown += `- **Methods**: ${res.methods.join(', ')}\n`
|
|
96
|
+
markdown += `- **Primary Label**: \`${res.labelField}\`\n\n`
|
|
97
|
+
markdown += `| Field | Type | Required | Writable | Details |\n`
|
|
98
|
+
markdown += `| :--- | :--- | :--- | :--- | :--- |\n`
|
|
99
|
+
|
|
100
|
+
res.fields.forEach((f) => {
|
|
101
|
+
const details = f.isEnum && f.options ? `Options: ${f.options.join(', ')}` : (f.references ? `Refs: ${f.references}` : '-')
|
|
102
|
+
markdown += `| ${f.name} | ${f.type} | ${f.required ? '✅' : '❌'} | ${f.isReadOnly ? '❌' : '✅'} | ${details} |\n`
|
|
103
|
+
})
|
|
104
|
+
markdown += `\n---\n`
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
return markdown
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return payload
|
|
111
|
+
})
|
|
@@ -15,6 +15,18 @@ export async function checkAdminAccess(event: H3Event, model: string, action: st
|
|
|
15
15
|
return true
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
// 1. Bearer Token or Query Check (Agentic/MCP Tooling)
|
|
19
|
+
const authHeader = getHeader(event, 'authorization')
|
|
20
|
+
const query = getQuery(event)
|
|
21
|
+
const apiToken = useRuntimeConfig(event).apiSecretToken
|
|
22
|
+
|
|
23
|
+
// Extract token from Header or fallback to Query param
|
|
24
|
+
const token = (authHeader?.startsWith('Bearer ') ? authHeader.split(' ')[1] : null) || query.token
|
|
25
|
+
|
|
26
|
+
if (token && apiToken && token === apiToken) {
|
|
27
|
+
return true
|
|
28
|
+
}
|
|
29
|
+
|
|
18
30
|
if (auth.type === 'jwt') {
|
|
19
31
|
if (!auth.jwtSecret) {
|
|
20
32
|
console.warn('JWT Secret is not configured but auth type is jwt')
|
|
@@ -113,15 +125,28 @@ export async function checkAdminAccess(event: H3Event, model: string, action: st
|
|
|
113
125
|
|
|
114
126
|
export async function ensureAuthenticated(event: H3Event): Promise<void> {
|
|
115
127
|
const { auth } = useAutoCrudConfig()
|
|
128
|
+
const runtimeConfig = useRuntimeConfig(event)
|
|
116
129
|
|
|
117
130
|
if (!auth?.authentication) return
|
|
118
131
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
132
|
+
// Extract Token: Priority 1: Authorization Header | Priority 2: Query String (?token=)
|
|
133
|
+
const authHeader = getHeader(event, 'authorization')
|
|
134
|
+
const query = getQuery(event)
|
|
135
|
+
const apiToken = runtimeConfig.apiSecretToken
|
|
136
|
+
|
|
137
|
+
const token = (authHeader?.startsWith('Bearer ') ? authHeader.split(' ')[1] : null) || query.token
|
|
138
|
+
|
|
139
|
+
// 1. API Token Check (Agentic/MCP)
|
|
140
|
+
if (token && apiToken && token === apiToken) {
|
|
123
141
|
return
|
|
124
142
|
}
|
|
125
143
|
|
|
144
|
+
// 2. JWT Check
|
|
145
|
+
if (auth.type === 'jwt' && auth.jwtSecret) {
|
|
146
|
+
if (await verifyJwtToken(event, auth.jwtSecret)) return
|
|
147
|
+
throw createError({ statusCode: 401, statusMessage: 'Unauthorized' })
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// 3. Session Check (Standard UI)
|
|
126
151
|
await (requireUserSession as (event: H3Event) => Promise<void>)(event)
|
|
127
152
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export const PROTECTED_FIELDS = [
|
|
2
|
+
'id',
|
|
3
|
+
'createdAt', 'updatedAt', 'deletedAt',
|
|
4
|
+
'createdBy', 'updatedBy', 'deletedBy',
|
|
5
|
+
'created_at', 'updated_at', 'deleted_at',
|
|
6
|
+
'created_by', 'updated_by', 'deleted_by',
|
|
7
|
+
]
|
|
8
|
+
|
|
9
|
+
export const HIDDEN_FIELDS = [
|
|
10
|
+
// Sensitive Auth
|
|
11
|
+
'password',
|
|
12
|
+
'resetToken', 'reset_token',
|
|
13
|
+
'resetExpires', 'reset_expires',
|
|
14
|
+
'githubId', 'github_id',
|
|
15
|
+
'googleId', 'google_id',
|
|
16
|
+
'secret',
|
|
17
|
+
'token',
|
|
18
|
+
// System Fields (Leakage prevention)
|
|
19
|
+
'deletedAt',
|
|
20
|
+
'createdBy',
|
|
21
|
+
'updatedBy',
|
|
22
|
+
'deleted_at',
|
|
23
|
+
'created_by',
|
|
24
|
+
'updated_by',
|
|
25
|
+
]
|