odac 1.1.0 → 1.2.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/.agent/rules/coding.md +27 -0
- package/.agent/rules/memory.md +33 -0
- package/.agent/rules/project.md +30 -0
- package/.agent/rules/workflow.md +16 -0
- package/.github/workflows/release.yml +42 -1
- package/.github/workflows/test-coverage.yml +6 -5
- package/.github/workflows/test-publish.yml +36 -0
- package/.husky/pre-commit +10 -0
- package/.husky/pre-push +13 -0
- package/.releaserc.js +3 -3
- package/CHANGELOG.md +67 -0
- package/README.md +16 -0
- package/bin/odac.js +182 -40
- package/client/odac.js +10 -4
- package/docs/backend/01-overview/03-development-server.md +38 -45
- package/docs/backend/02-structure/01-typical-project-layout.md +59 -26
- package/docs/backend/03-config/00-configuration-overview.md +6 -6
- package/docs/backend/03-config/01-database-connection.md +2 -2
- package/docs/backend/03-config/02-static-route-mapping-optional.md +1 -1
- package/docs/backend/03-config/03-request-timeout.md +1 -1
- package/docs/backend/03-config/04-environment-variables.md +4 -4
- package/docs/backend/03-config/05-early-hints.md +2 -2
- package/docs/backend/04-routing/03-api-and-data-routes.md +18 -0
- package/docs/backend/04-routing/07-cron-jobs.md +17 -1
- package/docs/backend/05-controllers/01-how-to-build-a-controller.md +48 -3
- package/docs/backend/05-controllers/03-controller-classes.md +40 -20
- package/docs/backend/06-request-and-response/01-the-request-object-what-is-the-user-asking-for.md +17 -0
- package/docs/backend/07-views/10-styling-and-tailwind.md +93 -0
- package/docs/backend/08-database/01-getting-started.md +2 -2
- package/docs/backend/10-authentication/03-register.md +1 -1
- package/docs/backend/10-authentication/04-odac-register-forms.md +2 -2
- package/docs/backend/10-authentication/05-session-management.md +15 -1
- package/docs/backend/10-authentication/06-odac-login-forms.md +2 -2
- package/docs/backend/10-authentication/07-magic-links.md +1 -1
- package/docs/index.json +5 -1
- package/jest.config.js +1 -1
- package/package.json +9 -5
- package/src/Auth.js +58 -23
- package/src/Config.js +7 -7
- package/src/Env.js +3 -1
- package/src/Ipc.js +7 -0
- package/src/Lang.js +9 -2
- package/src/Odac.js +44 -35
- package/src/Request.js +1 -1
- package/src/Route/Cron.js +58 -17
- package/src/Route/Internal.js +1 -1
- package/src/Route.js +282 -99
- package/src/Server.js +40 -3
- package/src/Storage.js +4 -0
- package/src/Token.js +6 -4
- package/src/Validator.js +1 -1
- package/src/Var.js +22 -6
- package/src/View/EarlyHints.js +43 -33
- package/src/View/Form.js +17 -11
- package/src/View.js +62 -6
- package/template/package.json +3 -1
- package/template/view/content/home.html +3 -3
- package/template/view/head/main.html +2 -2
- package/test/Client.test.js +168 -0
- package/test/Config.test.js +112 -0
- package/test/Lang.test.js +92 -0
- package/test/Odac.test.js +86 -0
- package/test/{framework/middleware.test.js → Route/Middleware.test.js} +2 -2
- package/test/{framework/Route.test.js → Route.test.js} +1 -1
- package/test/{framework/View → View}/EarlyHints.test.js +1 -1
- package/test/{framework/WebSocket.test.js → WebSocket.test.js} +2 -2
- package/test/scripts/check-coverage.js +4 -4
- package/test/cli/Cli.test.js +0 -36
- package/test/core/Commands.test.js +0 -538
- package/test/core/Config.test.js +0 -1432
- package/test/core/Lang.test.js +0 -250
- package/test/core/Odac.test.js +0 -234
- package/test/core/Process.test.js +0 -156
- package/test/server/Api.test.js +0 -647
- package/test/server/DNS.test.js +0 -2050
- package/test/server/DNS.test.js.bak +0 -2084
- package/test/server/Hub.test.js +0 -497
- package/test/server/Log.test.js +0 -73
- package/test/server/Mail.account.test_.js +0 -460
- package/test/server/Mail.init.test_.js +0 -411
- package/test/server/Mail.test_.js +0 -1340
- package/test/server/SSL.test_.js +0 -1491
- package/test/server/Server.test.js +0 -765
- package/test/server/Service.test_.js +0 -1127
- package/test/server/Subdomain.test.js +0 -440
- package/test/server/Web/Firewall.test.js +0 -175
- package/test/server/Web/Proxy.test.js +0 -397
- package/test/server/Web.test.js +0 -1494
- package/test/server/__mocks__/acme-client.js +0 -17
- package/test/server/__mocks__/bcrypt.js +0 -50
- package/test/server/__mocks__/child_process.js +0 -389
- package/test/server/__mocks__/crypto.js +0 -432
- package/test/server/__mocks__/fs.js +0 -450
- package/test/server/__mocks__/globalOdac.js +0 -227
- package/test/server/__mocks__/http.js +0 -575
- package/test/server/__mocks__/https.js +0 -272
- package/test/server/__mocks__/index.js +0 -249
- package/test/server/__mocks__/mail/server.js +0 -100
- package/test/server/__mocks__/mail/smtp.js +0 -31
- package/test/server/__mocks__/mailparser.js +0 -81
- package/test/server/__mocks__/net.js +0 -369
- package/test/server/__mocks__/node-forge.js +0 -328
- package/test/server/__mocks__/os.js +0 -320
- package/test/server/__mocks__/path.js +0 -291
- package/test/server/__mocks__/selfsigned.js +0 -8
- package/test/server/__mocks__/server/src/mail/server.js +0 -100
- package/test/server/__mocks__/server/src/mail/smtp.js +0 -31
- package/test/server/__mocks__/smtp-server.js +0 -106
- package/test/server/__mocks__/sqlite3.js +0 -394
- package/test/server/__mocks__/testFactories.js +0 -299
- package/test/server/__mocks__/testHelpers.js +0 -363
- package/test/server/__mocks__/tls.js +0 -229
- /package/template/{config.json → odac.json} +0 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# 🎨 Styling & Tailwind CSS
|
|
2
|
+
|
|
3
|
+
Odac comes with built-in, **Zero-Config** support for Tailwind CSS v4. This means you can start building beautiful, modern interfaces right out of the box without messing with configuration files or build pipelines.
|
|
4
|
+
|
|
5
|
+
## How it Works
|
|
6
|
+
|
|
7
|
+
The framework adopts a "Convention over Configuration" approach:
|
|
8
|
+
|
|
9
|
+
1. **Development (`npm run dev`)**:
|
|
10
|
+
* The framework automatically watches your HTML, JS, and View files.
|
|
11
|
+
* It compiles your CSS classes using the high-performance Rust-based Tailwind CLI.
|
|
12
|
+
* Changes are reflected instantly.
|
|
13
|
+
|
|
14
|
+
2. **Production (`npm run build`)**:
|
|
15
|
+
* The framework scans your project, builds the final CSS, and minifies it.
|
|
16
|
+
* The output is saved to `public/assets/css/app.css`.
|
|
17
|
+
|
|
18
|
+
3. **Serving (`npm start`)**:
|
|
19
|
+
* The compiled CSS file is served statically. No background processes, no overhead.
|
|
20
|
+
|
|
21
|
+
## Customizing CSS
|
|
22
|
+
|
|
23
|
+
By default, Odac manages everything for you internally. However, if you need to add custom CSS, fonts, or Tailwind configuration (like `@theme`), you can do so easily.
|
|
24
|
+
|
|
25
|
+
### The Source File
|
|
26
|
+
|
|
27
|
+
Simply create a file at **`view/css/app.css`**.
|
|
28
|
+
|
|
29
|
+
If this file exists, Odac will use it as the **source** (input) for Tailwind.
|
|
30
|
+
|
|
31
|
+
**Example `view/css/app.css`:**
|
|
32
|
+
|
|
33
|
+
```css
|
|
34
|
+
@import "tailwindcss";
|
|
35
|
+
|
|
36
|
+
@theme {
|
|
37
|
+
--font-display: "Satoshi", "sans-serif";
|
|
38
|
+
--color-brand: #ff5733;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/* Your custom CSS rules */
|
|
42
|
+
.hero-gradient {
|
|
43
|
+
background: linear-gradient(to right, var(--color-brand), #ff0000);
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### The Output File
|
|
48
|
+
|
|
49
|
+
The compiled CSS is always output to:
|
|
50
|
+
**`public/assets/css/app.css`**
|
|
51
|
+
|
|
52
|
+
> **⚠️ Important:** Never edit `public/assets/css/app.css` manually. It is a generated file and will be overwritten by Odac during build or development. Always edit `view/css/app.css` instead.
|
|
53
|
+
|
|
54
|
+
## HTML Integration
|
|
55
|
+
|
|
56
|
+
In your layout files (e.g., `view/head/main.html`), simply link to the compiled asset:
|
|
57
|
+
|
|
58
|
+
```html
|
|
59
|
+
<link rel="stylesheet" href="/assets/css/app.css" />
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
This is already set up for you in the default project template.
|
|
63
|
+
|
|
64
|
+
## Multiple CSS Files
|
|
65
|
+
|
|
66
|
+
Odac supports multiple CSS entry points. If your application has distinct sections (e.g., a Landing Page, a Dashboard, and an Admin Panel) that require separate stylesheets, you can organize them easily.
|
|
67
|
+
|
|
68
|
+
### How to use
|
|
69
|
+
|
|
70
|
+
Any `.css` file you place in the **`view/css/`** directory will be automatically detected, watched, and compiled by Odac.
|
|
71
|
+
|
|
72
|
+
**Input:**
|
|
73
|
+
* `view/css/app.css`
|
|
74
|
+
* `view/css/admin.css`
|
|
75
|
+
* `view/css/landing.css`
|
|
76
|
+
|
|
77
|
+
**Output (Compiled):**
|
|
78
|
+
* `public/assets/css/app.css`
|
|
79
|
+
* `public/assets/css/admin.css`
|
|
80
|
+
* `public/assets/css/landing.css`
|
|
81
|
+
|
|
82
|
+
You can then link each specific stylesheet in its respective layout file:
|
|
83
|
+
|
|
84
|
+
```html
|
|
85
|
+
<!-- In Admin Layout -->
|
|
86
|
+
<link rel="stylesheet" href="/assets/css/admin.css" />
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Using Tailwind v4
|
|
90
|
+
|
|
91
|
+
Tailwind v4 is radically simpler. You generally don't need a `tailwind.config.js` file anymore. You can define your theme directly in CSS using the `@theme` block as shown above.
|
|
92
|
+
|
|
93
|
+
However, Odac respects standard Tailwind behavior. If you absolutely need a config file for plugins or legacy reasons, you can create one, and the Tailwind CLI will detect it. But for 99% of use cases, the Zero-Config approach is cleaner and faster.
|
|
@@ -4,7 +4,7 @@ ODAC supports multiple database connections including **MySQL**, **PostgreSQL**
|
|
|
4
4
|
|
|
5
5
|
## Configuration
|
|
6
6
|
|
|
7
|
-
Add your database credentials to `
|
|
7
|
+
Add your database credentials to `odac.json`.
|
|
8
8
|
|
|
9
9
|
Supported configuration options:
|
|
10
10
|
|
|
@@ -79,7 +79,7 @@ DB_USER=myuser
|
|
|
79
79
|
DB_PASSWORD=mypassword
|
|
80
80
|
```
|
|
81
81
|
|
|
82
|
-
**
|
|
82
|
+
**odac.json:**
|
|
83
83
|
```json
|
|
84
84
|
{
|
|
85
85
|
"database": {
|
|
@@ -4,7 +4,7 @@ The `<odac:register>` component provides a zero-configuration way to create secu
|
|
|
4
4
|
|
|
5
5
|
## Quick Start
|
|
6
6
|
|
|
7
|
-
### 1. Configure Database (
|
|
7
|
+
### 1. Configure Database (odac.json)
|
|
8
8
|
|
|
9
9
|
```json
|
|
10
10
|
{
|
|
@@ -653,7 +653,7 @@ Then handle the response in JavaScript if needed (though not required for basic
|
|
|
653
653
|
|
|
654
654
|
### Form Not Submitting
|
|
655
655
|
|
|
656
|
-
- Check that `
|
|
656
|
+
- Check that `odac.json` has auth configuration
|
|
657
657
|
- Verify database table exists
|
|
658
658
|
- Check browser console for JavaScript errors
|
|
659
659
|
|
|
@@ -31,7 +31,7 @@ Sessions use a **sliding window** approach (similar to NextAuth.js):
|
|
|
31
31
|
|
|
32
32
|
### Configuration
|
|
33
33
|
|
|
34
|
-
Configure session behavior in `
|
|
34
|
+
Configure session behavior in `odac.json`:
|
|
35
35
|
|
|
36
36
|
```json
|
|
37
37
|
{
|
|
@@ -127,9 +127,23 @@ const isLoggedIn = await Odac.Auth.check()
|
|
|
127
127
|
**Get user info:**
|
|
128
128
|
```javascript
|
|
129
129
|
const user = Odac.Auth.user(null) // Full user object
|
|
130
|
+
const user = Odac.Auth.user(null) // Full user object
|
|
130
131
|
const email = Odac.Auth.user('email') // Specific field
|
|
131
132
|
```
|
|
132
133
|
|
|
134
|
+
### Custom Session Data
|
|
135
|
+
|
|
136
|
+
If you need to store your own data in the session (e.g. shopping cart ID, preferences), use the `Odac.session()` helper:
|
|
137
|
+
|
|
138
|
+
```javascript
|
|
139
|
+
// Store data
|
|
140
|
+
Odac.session('theme', 'dark')
|
|
141
|
+
|
|
142
|
+
// Retrieve data
|
|
143
|
+
const theme = Odac.session('theme')
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
|
|
133
147
|
### Best Practices
|
|
134
148
|
|
|
135
149
|
1. **Choose appropriate timeouts** based on your app's security needs
|
|
@@ -4,7 +4,7 @@ The `<odac:login>` component provides a zero-configuration way to create secure
|
|
|
4
4
|
|
|
5
5
|
## Quick Start
|
|
6
6
|
|
|
7
|
-
### 1. Configure Database (
|
|
7
|
+
### 1. Configure Database (odac.json)
|
|
8
8
|
|
|
9
9
|
```json
|
|
10
10
|
{
|
|
@@ -559,7 +559,7 @@ The login form supports multiple authentication methods:
|
|
|
559
559
|
|
|
560
560
|
### Form Not Submitting
|
|
561
561
|
|
|
562
|
-
- Check that `
|
|
562
|
+
- Check that `odac.json` has MySQL configuration
|
|
563
563
|
- Verify database table exists
|
|
564
564
|
- Check browser console for JavaScript errors
|
|
565
565
|
- Ensure CSRF token is valid
|
|
@@ -79,7 +79,7 @@ Odac handles the verification route automatically at `/_odac/magic-verify`. Howe
|
|
|
79
79
|
|
|
80
80
|
## Configuration
|
|
81
81
|
|
|
82
|
-
Magic links usage is configured in your `
|
|
82
|
+
Magic links usage is configured in your `odac.json` file under the `auth` object.
|
|
83
83
|
|
|
84
84
|
```json
|
|
85
85
|
{
|
package/docs/index.json
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
},
|
|
15
15
|
{
|
|
16
16
|
"file": "03-development-server.md",
|
|
17
|
-
"title": "
|
|
17
|
+
"title": "CLI Commands & Deployment"
|
|
18
18
|
}
|
|
19
19
|
]
|
|
20
20
|
},
|
|
@@ -201,6 +201,10 @@
|
|
|
201
201
|
{
|
|
202
202
|
"file": "09-comments.md",
|
|
203
203
|
"title": "Comments"
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
"file": "10-styling-and-tailwind.md",
|
|
207
|
+
"title": "Styling & Tailwind CSS"
|
|
204
208
|
}
|
|
205
209
|
]
|
|
206
210
|
},
|
package/jest.config.js
CHANGED
|
@@ -5,7 +5,7 @@ const config = {
|
|
|
5
5
|
testMatch: ['**/test/**/*.test.js', '**/?(*.)+(spec|test).js'],
|
|
6
6
|
collectCoverage: true,
|
|
7
7
|
coverageDirectory: 'coverage',
|
|
8
|
-
collectCoverageFrom: ['
|
|
8
|
+
collectCoverageFrom: ['src/**/*.js', 'client/**/*.js']
|
|
9
9
|
// Coverage thresholds disabled - focus on test pass/fail only
|
|
10
10
|
// Coverage is still collected and reported, but doesn't block commits
|
|
11
11
|
// This allows incremental test development without artificial barriers
|
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"email": "mail@emre.red",
|
|
8
8
|
"url": "https://emre.red"
|
|
9
9
|
},
|
|
10
|
-
"version": "1.
|
|
10
|
+
"version": "1.2.0",
|
|
11
11
|
"license": "MIT",
|
|
12
12
|
"engines": {
|
|
13
13
|
"node": ">=18.0.0"
|
|
@@ -21,13 +21,17 @@
|
|
|
21
21
|
"url": "https://github.com/odac-run/odac.js.git"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"
|
|
25
|
-
"bcrypt": "^5.1.1",
|
|
24
|
+
"@tailwindcss/cli": "^4.1.18",
|
|
26
25
|
"knex": "^3.1.0",
|
|
27
26
|
"lmdb": "^3.4.4",
|
|
28
27
|
"mysql2": "^3.16.0",
|
|
29
28
|
"pg": "^8.16.3",
|
|
30
|
-
"redis": "^5.10.0"
|
|
29
|
+
"redis": "^5.10.0",
|
|
30
|
+
"tailwindcss": "^4.1.18"
|
|
31
|
+
},
|
|
32
|
+
"overrides": {
|
|
33
|
+
"tar": "^7.5.7",
|
|
34
|
+
"undici": "^6.23.0"
|
|
31
35
|
},
|
|
32
36
|
"devDependencies": {
|
|
33
37
|
"@eslint/js": "^9.33.0",
|
|
@@ -35,7 +39,7 @@
|
|
|
35
39
|
"@semantic-release/commit-analyzer": "^13.0.1",
|
|
36
40
|
"@semantic-release/git": "^10.0.1",
|
|
37
41
|
"@semantic-release/github": "^12.0.2",
|
|
38
|
-
"@semantic-release/npm": "^13.1.
|
|
42
|
+
"@semantic-release/npm": "^13.1.2",
|
|
39
43
|
"@semantic-release/release-notes-generator": "^14.1.0",
|
|
40
44
|
"conventional-changelog-conventionalcommits": "^9.1.0",
|
|
41
45
|
"eslint": "^9.33.0",
|
package/src/Auth.js
CHANGED
|
@@ -35,29 +35,54 @@ class Auth {
|
|
|
35
35
|
|
|
36
36
|
// Knex build queries differently than previous builder
|
|
37
37
|
// Need to chain where clauses
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
// Resolve input promises upfront to avoid side effects and race conditions
|
|
39
|
+
const criteria = {}
|
|
40
|
+
const keys = Object.keys(where)
|
|
41
|
+
|
|
42
|
+
if (keys.length === 0) return false
|
|
43
|
+
|
|
44
|
+
for (const key of keys) {
|
|
45
|
+
criteria[key] = where[key] instanceof Promise ? await where[key] : where[key]
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Chain where clauses
|
|
49
|
+
for (const key in criteria) {
|
|
50
|
+
query = query.orWhere(key, criteria[key])
|
|
40
51
|
}
|
|
41
52
|
|
|
42
53
|
// Execute query
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (!
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
for (
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
const candidates = await query
|
|
55
|
+
|
|
56
|
+
if (!candidates || candidates.length === 0) return false
|
|
57
|
+
|
|
58
|
+
// Iterate candidates to find the exact match
|
|
59
|
+
candidateLoop: for (const user of candidates) {
|
|
60
|
+
for (const key of keys) {
|
|
61
|
+
const userValue = user[key]
|
|
62
|
+
const targetValue = criteria[key]
|
|
63
|
+
|
|
64
|
+
if (!userValue) continue candidateLoop
|
|
65
|
+
|
|
66
|
+
// Strict equality check
|
|
67
|
+
if (userValue === targetValue) continue
|
|
68
|
+
|
|
69
|
+
// Security: Check hashed fields (Bcrypt/MD5)
|
|
70
|
+
const valueHandler = Odac.Var(userValue)
|
|
71
|
+
let hashMatch = false
|
|
72
|
+
|
|
73
|
+
if (valueHandler.is('hash')) {
|
|
74
|
+
hashMatch = valueHandler.hashCheck(targetValue)
|
|
75
|
+
} else if (valueHandler.is('md5')) {
|
|
76
|
+
hashMatch = Odac.Var(targetValue).md5() === userValue
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (!hashMatch) continue candidateLoop
|
|
56
80
|
}
|
|
57
|
-
|
|
81
|
+
|
|
82
|
+
return user
|
|
58
83
|
}
|
|
59
|
-
|
|
60
|
-
return
|
|
84
|
+
|
|
85
|
+
return false
|
|
61
86
|
} else if (this.#user) {
|
|
62
87
|
return true
|
|
63
88
|
} else {
|
|
@@ -126,12 +151,15 @@ class Auth {
|
|
|
126
151
|
|
|
127
152
|
this.#cleanupExpiredTokens(token)
|
|
128
153
|
|
|
129
|
-
|
|
154
|
+
// Generate secure token using generic CSPRNG (Cryptographically Secure Pseudo-Random Number Generator)
|
|
155
|
+
// Why: Math.random() is predictable and MD5 is a broken hashing algorithm.
|
|
156
|
+
// We use 32 bytes (256 bits) of entropy which is industry standard.
|
|
157
|
+
let token_y = nodeCrypto.randomBytes(32).toString('hex')
|
|
130
158
|
|
|
131
159
|
let cookie = {
|
|
132
160
|
id: Odac.DB.nanoid(),
|
|
133
161
|
user: user[key],
|
|
134
|
-
token_x:
|
|
162
|
+
token_x: nodeCrypto.randomBytes(32).toString('hex'),
|
|
135
163
|
token_y: Odac.Var(token_y).hash(),
|
|
136
164
|
browser: this.#request.header('user-agent'),
|
|
137
165
|
ip: this.#request.ip
|
|
@@ -182,7 +210,7 @@ class Auth {
|
|
|
182
210
|
return {success: false, error: 'Invalid data provided'}
|
|
183
211
|
}
|
|
184
212
|
|
|
185
|
-
if (data[passwordField] && !Odac.Var(data[passwordField]).is('
|
|
213
|
+
if (data[passwordField] && !Odac.Var(data[passwordField]).is('hash')) {
|
|
186
214
|
data[passwordField] = Odac.Var(data[passwordField]).hash()
|
|
187
215
|
}
|
|
188
216
|
|
|
@@ -557,10 +585,17 @@ class Auth {
|
|
|
557
585
|
})
|
|
558
586
|
}
|
|
559
587
|
|
|
560
|
-
|
|
588
|
+
/**
|
|
589
|
+
* Retrieves the authenticated user or a specific column.
|
|
590
|
+
* Why: To provide access to the current user's session data securely.
|
|
591
|
+
*
|
|
592
|
+
* @param {string|null} [col=null] - The column to retrieve, or null for the full user object.
|
|
593
|
+
* @returns {object|string|number|boolean|false} The user object, column value, or false if not logged in.
|
|
594
|
+
*/
|
|
595
|
+
user(col = null) {
|
|
561
596
|
if (!this.#user) return false
|
|
562
597
|
if (col === null) return this.#user
|
|
563
|
-
|
|
598
|
+
return this.#user[col]
|
|
564
599
|
}
|
|
565
600
|
}
|
|
566
601
|
|
package/src/Config.js
CHANGED
|
@@ -5,13 +5,13 @@ const os = require('os')
|
|
|
5
5
|
module.exports = {
|
|
6
6
|
auth: {
|
|
7
7
|
key: 'id',
|
|
8
|
-
token: 'odac_auth'
|
|
8
|
+
token: 'odac_auth' // This is the TABLE NAME for tokens, not a secret token.
|
|
9
9
|
},
|
|
10
10
|
request: {
|
|
11
11
|
timeout: 10000
|
|
12
12
|
},
|
|
13
13
|
encrypt: {
|
|
14
|
-
key: 'odac'
|
|
14
|
+
key: 'odac' // Default encryption key. MUST be overridden in production.
|
|
15
15
|
},
|
|
16
16
|
earlyHints: {
|
|
17
17
|
enabled: true,
|
|
@@ -22,7 +22,7 @@ module.exports = {
|
|
|
22
22
|
driver: 'memory',
|
|
23
23
|
redis: 'default'
|
|
24
24
|
},
|
|
25
|
-
debug:
|
|
25
|
+
debug: process.env.NODE_ENV !== 'production',
|
|
26
26
|
|
|
27
27
|
init: function () {
|
|
28
28
|
try {
|
|
@@ -31,17 +31,17 @@ module.exports = {
|
|
|
31
31
|
this.system = {}
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
if (fs.existsSync(__dir + '/
|
|
34
|
+
if (fs.existsSync(__dir + '/odac.json')) {
|
|
35
35
|
let config = {}
|
|
36
36
|
try {
|
|
37
|
-
config = JSON.parse(fs.readFileSync(__dir + '/
|
|
37
|
+
config = JSON.parse(fs.readFileSync(__dir + '/odac.json'))
|
|
38
38
|
config = this._interpolate(config)
|
|
39
39
|
} catch (err) {
|
|
40
|
-
console.error('Error reading config file:', __dir + '/
|
|
40
|
+
console.error('Error reading config file:', __dir + '/odac.json', err.message)
|
|
41
41
|
}
|
|
42
42
|
this._deepMerge(this, config)
|
|
43
43
|
}
|
|
44
|
-
this.encrypt.key = nodeCrypto.createHash('
|
|
44
|
+
this.encrypt.key = nodeCrypto.createHash('sha256').update(this.encrypt.key).digest()
|
|
45
45
|
},
|
|
46
46
|
|
|
47
47
|
_interpolate: function (obj) {
|
package/src/Env.js
CHANGED
|
@@ -20,7 +20,9 @@ module.exports = {
|
|
|
20
20
|
// Parse quoted values
|
|
21
21
|
value = this._parseValue(value)
|
|
22
22
|
|
|
23
|
-
process.env[key]
|
|
23
|
+
if (process.env[key] === undefined) {
|
|
24
|
+
process.env[key] = value
|
|
25
|
+
}
|
|
24
26
|
})
|
|
25
27
|
} catch (err) {
|
|
26
28
|
console.error('Error reading .env file:', err.message)
|
package/src/Ipc.js
CHANGED
|
@@ -10,6 +10,13 @@ class Ipc extends EventEmitter {
|
|
|
10
10
|
this._subs = new Map() // For memory driver subscriptions
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* ARCHITECTURE NOTE:
|
|
15
|
+
* This module implements a "Primary-Replica" pattern for the 'memory' driver.
|
|
16
|
+
* - The Primary process holds the 'Source of Truth' in local Maps.
|
|
17
|
+
* - Workers communicate via IPC (process.send) to read/write to this central store.
|
|
18
|
+
* This ensures state consistency across the cluster without needing Redis.
|
|
19
|
+
*/
|
|
13
20
|
async init() {
|
|
14
21
|
if (this.initialized) return
|
|
15
22
|
this.initialized = true
|
package/src/Lang.js
CHANGED
|
@@ -43,9 +43,16 @@ class Lang {
|
|
|
43
43
|
|
|
44
44
|
set(lang) {
|
|
45
45
|
if (!lang || lang.length !== 2 || !this.#odac.Var(lang).is('alpha')) {
|
|
46
|
-
if (
|
|
46
|
+
if (
|
|
47
|
+
this.#odac.Request &&
|
|
48
|
+
this.#odac.Request.header &&
|
|
49
|
+
this.#odac.Request.header('ACCEPT-LANGUAGE') &&
|
|
50
|
+
this.#odac.Request.header('ACCEPT-LANGUAGE').length > 1
|
|
51
|
+
) {
|
|
47
52
|
lang = this.#odac.Request.header('ACCEPT-LANGUAGE').substr(0, 2)
|
|
48
|
-
else
|
|
53
|
+
} else {
|
|
54
|
+
lang = this.#odac.Config.lang?.default || 'en'
|
|
55
|
+
}
|
|
49
56
|
}
|
|
50
57
|
this.#lang = lang
|
|
51
58
|
if (fs.existsSync(__dir + '/storage/language/' + lang + '.json'))
|
package/src/Odac.js
CHANGED
|
@@ -64,9 +64,11 @@ module.exports = {
|
|
|
64
64
|
_odac.Var = (...args) => new (require('./Var.js'))(...args)
|
|
65
65
|
|
|
66
66
|
if (req) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
if (typeof req === 'object') {
|
|
68
|
+
_odac.Request = new (require('./Request.js'))(id, req, res, _odac)
|
|
69
|
+
_odac.Auth = new (require('./Auth.js'))(_odac.Request)
|
|
70
|
+
_odac.Token = new (require('./Token.js'))(_odac.Request)
|
|
71
|
+
}
|
|
70
72
|
_odac.Lang = new (require('./Lang.js'))(_odac)
|
|
71
73
|
if (res) {
|
|
72
74
|
_odac.View = new (require('./View.js'))(_odac)
|
|
@@ -110,42 +112,49 @@ module.exports = {
|
|
|
110
112
|
_odac.__ = function (...args) {
|
|
111
113
|
return _odac.Lang.get(...args)
|
|
112
114
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
115
|
+
if (typeof req === 'object') {
|
|
116
|
+
_odac.abort = function (code) {
|
|
117
|
+
return _odac.Request.abort(code)
|
|
118
|
+
}
|
|
119
|
+
_odac.cookie = function (key, value, options) {
|
|
120
|
+
return _odac.Request.cookie(key, value, options)
|
|
121
|
+
}
|
|
122
|
+
_odac.direct = function (url) {
|
|
123
|
+
return _odac.Request.redirect(url)
|
|
124
|
+
}
|
|
121
125
|
}
|
|
122
126
|
_odac.env = function (key, defaultValue) {
|
|
123
127
|
return _odac.Env.get(key, defaultValue)
|
|
124
128
|
}
|
|
125
|
-
|
|
126
|
-
return
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
_odac.
|
|
148
|
-
|
|
129
|
+
if (typeof req === 'object') {
|
|
130
|
+
_odac.return = function (data) {
|
|
131
|
+
return _odac.Request.end(data)
|
|
132
|
+
}
|
|
133
|
+
_odac.request = function (key) {
|
|
134
|
+
return _odac.Request.request(key)
|
|
135
|
+
}
|
|
136
|
+
_odac.set = function (key, value) {
|
|
137
|
+
return _odac.Request.set(key, value)
|
|
138
|
+
}
|
|
139
|
+
_odac.session = function (key, value) {
|
|
140
|
+
return _odac.Request.session(key, value)
|
|
141
|
+
}
|
|
142
|
+
_odac.share = function (key, value) {
|
|
143
|
+
return _odac.Request.share(key, value)
|
|
144
|
+
}
|
|
145
|
+
_odac.token = function (hash) {
|
|
146
|
+
return hash ? _odac.Token.check(hash) : _odac.Token.generate()
|
|
147
|
+
}
|
|
148
|
+
_odac.validator = function () {
|
|
149
|
+
return new (require('./Validator.js'))(_odac.Request)
|
|
150
|
+
}
|
|
151
|
+
_odac.write = function (value) {
|
|
152
|
+
return _odac.Request.write(value)
|
|
153
|
+
}
|
|
154
|
+
_odac.stream = function (input) {
|
|
155
|
+
_odac.Request.clearTimeout()
|
|
156
|
+
return new (require('./Stream'))(_odac.Request.req, _odac.Request.res, input, _odac)
|
|
157
|
+
}
|
|
149
158
|
}
|
|
150
159
|
|
|
151
160
|
if (global.Odac?.Route?.class) {
|
package/src/Request.js
CHANGED
|
@@ -71,7 +71,7 @@ class OdacRequest {
|
|
|
71
71
|
if (options.expires === undefined) options.expires = new Date(Date.now() + 1000 * 60 * 60 * 24 * 365).toUTCString()
|
|
72
72
|
if (options.secure === undefined) options.secure = true
|
|
73
73
|
if (options.httpOnly === undefined) options.httpOnly = true
|
|
74
|
-
if (options.sameSite === undefined) options.sameSite = '
|
|
74
|
+
if (options.sameSite === undefined) options.sameSite = 'Lax'
|
|
75
75
|
if (typeof value === 'object') value = JSON.stringify(value)
|
|
76
76
|
let cookie = `${key}=${value}`
|
|
77
77
|
for (const option of Object.keys(options)) if (options[option]) cookie += `; ${option}=${options[option]}`
|