odac 1.1.0 → 1.3.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 +42 -0
- package/.agent/rules/project.md +30 -0
- package/.agent/rules/workflow.md +16 -0
- package/.github/workflows/release.yml +68 -1
- package/.github/workflows/test-coverage.yml +6 -5
- package/.husky/pre-commit +10 -0
- package/.husky/pre-push +13 -0
- package/.releaserc.js +1 -1
- package/CHANGELOG.md +99 -0
- package/README.md +16 -0
- package/bin/odac.js +196 -38
- package/client/odac.js +238 -171
- 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/04-routing/09-websocket.md +14 -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 +18 -14
- package/src/Auth.js +58 -23
- package/src/Config.js +7 -7
- package/src/Database.js +1 -1
- package/src/Env.js +3 -1
- package/src/Ipc.js +7 -0
- package/src/Lang.js +9 -2
- package/src/Mail.js +19 -9
- package/src/Odac.js +56 -44
- package/src/Request.js +6 -2
- package/src/Route/Cron.js +58 -17
- package/src/Route/Internal.js +1 -1
- package/src/Route.js +375 -125
- package/src/Server.js +40 -3
- package/src/Storage.js +4 -0
- package/src/Token.js +6 -4
- package/src/Validator.js +24 -15
- package/src/Var.js +22 -6
- package/src/View/EarlyHints.js +43 -33
- package/src/View/Form.js +17 -11
- package/src/View.js +68 -12
- package/src/WebSocket.js +45 -12
- 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 +88 -0
- package/test/{framework/middleware.test.js → Route/Middleware.test.js} +2 -2
- package/test/{framework/Route.test.js → Route.test.js} +105 -1
- package/test/{framework/View → View}/EarlyHints.test.js +1 -1
- package/test/WebSocket.test.js +238 -0
- 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/framework/WebSocket.test.js +0 -100
- 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.3.0",
|
|
11
11
|
"license": "MIT",
|
|
12
12
|
"engines": {
|
|
13
13
|
"node": ">=18.0.0"
|
|
@@ -21,32 +21,36 @@
|
|
|
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.9",
|
|
34
|
+
"cross-spawn": "7.0.6"
|
|
31
35
|
},
|
|
32
36
|
"devDependencies": {
|
|
33
|
-
"@eslint/js": "^9.
|
|
37
|
+
"@eslint/js": "^9.39.2",
|
|
34
38
|
"@semantic-release/changelog": "^6.0.3",
|
|
35
39
|
"@semantic-release/commit-analyzer": "^13.0.1",
|
|
36
40
|
"@semantic-release/git": "^10.0.1",
|
|
37
|
-
"@semantic-release/github": "^12.0.
|
|
38
|
-
"@semantic-release/npm": "^13.1.
|
|
41
|
+
"@semantic-release/github": "^12.0.6",
|
|
42
|
+
"@semantic-release/npm": "^13.1.4",
|
|
39
43
|
"@semantic-release/release-notes-generator": "^14.1.0",
|
|
40
44
|
"conventional-changelog-conventionalcommits": "^9.1.0",
|
|
41
|
-
"eslint": "^9.
|
|
45
|
+
"eslint": "^9.39.2",
|
|
42
46
|
"eslint-config-prettier": "^10.1.8",
|
|
43
|
-
"eslint-plugin-prettier": "^5.5.
|
|
44
|
-
"globals": "^
|
|
47
|
+
"eslint-plugin-prettier": "^5.5.5",
|
|
48
|
+
"globals": "^17.3.0",
|
|
45
49
|
"husky": "^9.1.7",
|
|
46
|
-
"jest": "^30.0
|
|
47
|
-
"lint-staged": "^16.
|
|
48
|
-
"prettier": "3.
|
|
49
|
-
"semantic-release": "^25.0.
|
|
50
|
+
"jest": "^30.2.0",
|
|
51
|
+
"lint-staged": "^16.2.7",
|
|
52
|
+
"prettier": "^3.8.1",
|
|
53
|
+
"semantic-release": "^25.0.3",
|
|
50
54
|
"sqlite3": "^5.1.7"
|
|
51
55
|
},
|
|
52
56
|
"scripts": {
|
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/Database.js
CHANGED
|
@@ -18,7 +18,7 @@ class DatabaseManager {
|
|
|
18
18
|
if (db.type === 'postgres' || db.type === 'pg' || db.type === 'postgresql') client = 'pg'
|
|
19
19
|
if (db.type === 'sqlite' || db.type === 'sqlite3') client = 'sqlite3'
|
|
20
20
|
|
|
21
|
-
let connectionConfig
|
|
21
|
+
let connectionConfig
|
|
22
22
|
|
|
23
23
|
if (client === 'sqlite3') {
|
|
24
24
|
connectionConfig = {
|
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/Mail.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const net = require('net')
|
|
2
2
|
const nodeCrypto = require('crypto')
|
|
3
3
|
const fs = require('fs')
|
|
4
|
+
const fsPromises = fs.promises
|
|
4
5
|
// const Form = require('./View/Form')
|
|
5
6
|
|
|
6
7
|
const CACHE_DIR = './storage/.cache'
|
|
@@ -155,13 +156,14 @@ class Mail {
|
|
|
155
156
|
}
|
|
156
157
|
|
|
157
158
|
async #render(file, data) {
|
|
158
|
-
const fd = fs.openSync(file, 'r')
|
|
159
159
|
let mtime, content
|
|
160
|
+
const handle = await fsPromises.open(file, 'r')
|
|
160
161
|
try {
|
|
161
|
-
|
|
162
|
-
|
|
162
|
+
const stats = await handle.stat()
|
|
163
|
+
mtime = stats.mtimeMs
|
|
164
|
+
content = await handle.readFile('utf8')
|
|
163
165
|
} finally {
|
|
164
|
-
|
|
166
|
+
await handle.close()
|
|
165
167
|
}
|
|
166
168
|
|
|
167
169
|
// Since mail doesn't have a persistent Odac instance access like View cache, we manage a simple cache or just re-compile.
|
|
@@ -251,8 +253,8 @@ class Mail {
|
|
|
251
253
|
}
|
|
252
254
|
|
|
253
255
|
let cache = `${nodeCrypto.createHash('md5').update(file).digest('hex')}`
|
|
254
|
-
|
|
255
|
-
|
|
256
|
+
await fsPromises.mkdir(CACHE_DIR, {recursive: true})
|
|
257
|
+
await fsPromises.writeFile(
|
|
256
258
|
`${CACHE_DIR}/${cache}`,
|
|
257
259
|
`module.exports = async (Odac, data, get, __) => {\n
|
|
258
260
|
// Destructure data keys into local scope variables
|
|
@@ -351,7 +353,9 @@ class Mail {
|
|
|
351
353
|
let textContent = ''
|
|
352
354
|
|
|
353
355
|
if (this.#template) {
|
|
354
|
-
|
|
356
|
+
try {
|
|
357
|
+
await fsPromises.access(__dir + '/view/mail/' + this.#template + '.html')
|
|
358
|
+
} catch {
|
|
355
359
|
console.error(`[Mail] Template not found: ${__dir}/view/mail/${this.#template}.html`)
|
|
356
360
|
return resolve(false)
|
|
357
361
|
}
|
|
@@ -409,7 +413,7 @@ class Mail {
|
|
|
409
413
|
]
|
|
410
414
|
}
|
|
411
415
|
|
|
412
|
-
const socketPath = process.env.ODAC_API_SOCKET || '/
|
|
416
|
+
const socketPath = process.env.ODAC_API_SOCKET || '/odac/api.sock'
|
|
413
417
|
|
|
414
418
|
if (Odac.Config.debug) console.log(`[Mail] Connecting to Odac Core via Unix Socket: ${socketPath}...`)
|
|
415
419
|
|
|
@@ -431,7 +435,13 @@ class Mail {
|
|
|
431
435
|
})
|
|
432
436
|
|
|
433
437
|
client.on('error', error => {
|
|
434
|
-
|
|
438
|
+
if (error.code === 'ENOENT' && error.address === socketPath) {
|
|
439
|
+
console.error(
|
|
440
|
+
'[Mail] Socket Error: If you are using ODAC, you must grant permissions or enter SMTP information in the config.'
|
|
441
|
+
)
|
|
442
|
+
} else {
|
|
443
|
+
console.error('[Mail] Socket Error:', error)
|
|
444
|
+
}
|
|
435
445
|
resolve(false)
|
|
436
446
|
})
|
|
437
447
|
|