@javagt/express-easy-auth 1.0.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/.env.example +13 -0
- package/demo/profileRouter.js +64 -0
- package/demo/public/css/style.css +1293 -0
- package/demo/public/index.html +272 -0
- package/demo/public/js/app.js +540 -0
- package/demo/server.js +195 -0
- package/examples/01-basic-setup.js +118 -0
- package/examples/02-passkeys.js +106 -0
- package/examples/03-api-keys.js +108 -0
- package/examples/04-totp-setup.js +125 -0
- package/examples/05-custom-logger.js +105 -0
- package/examples/06-password-reset.js +104 -0
- package/examples/08-external-db-linking.js +158 -0
- package/examples/README.md +32 -0
- package/openapi.yaml +263 -0
- package/package.json +35 -0
- package/readme.md +165 -0
- package/scratch/debug_bindings.js +29 -0
- package/scratch/test_sqlite.js +7 -0
- package/scratch/test_sqlite_multargs.js +17 -0
- package/scratch/test_sqlite_undefined.js +9 -0
- package/scratch/verify_sqlite_fix.js +14 -0
- package/src/client.js +295 -0
- package/src/db/init.js +203 -0
- package/src/db/sessionStore.js +67 -0
- package/src/index.js +61 -0
- package/src/middleware/auth.js +111 -0
- package/src/routes/auth.js +569 -0
- package/src/utils/authHelpers.js +48 -0
- package/src/utils/logger.js +71 -0
- package/test/auth.test.js +32 -0
- package/test/passkeys.test.js +19 -0
- package/test/user.test.js +29 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example 05: Custom Logger
|
|
3
|
+
*
|
|
4
|
+
* Shows how to plug in a custom logger implementation (e.g. for Winston or Pino)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import express from 'express';
|
|
8
|
+
import { setupAuth, authRouter, DefaultLogger } from '../src/index.js';
|
|
9
|
+
|
|
10
|
+
const app = express();
|
|
11
|
+
app.use(express.json());
|
|
12
|
+
|
|
13
|
+
// 1. Define a custom logger
|
|
14
|
+
// It must implement: error, warn, info, debug
|
|
15
|
+
class MyCustomLogger {
|
|
16
|
+
error(msg, meta) {
|
|
17
|
+
console.log(`[MY-APP-ERROR] ${msg}`, meta.err?.stack || '');
|
|
18
|
+
// You could send to Sentry, Datadog, etc. here
|
|
19
|
+
}
|
|
20
|
+
warn(msg, meta) { console.log(`[MY-APP-WARN] ${msg}`); }
|
|
21
|
+
info(msg, meta) { console.log(`[MY-APP-INFO] ${msg}`); }
|
|
22
|
+
debug(msg, meta) { console.log(`[MY-APP-DEBUG] ${msg}`); }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// 2. Or extend the DefaultLogger to keep DB logging but change console output
|
|
26
|
+
class EnhancedLogger extends DefaultLogger {
|
|
27
|
+
info(msg, meta) {
|
|
28
|
+
super.info(msg, meta); // Keep default behavior (DB + Console)
|
|
29
|
+
console.log('--- Custom log decorator ---');
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// 3. Initialize with custom logger
|
|
34
|
+
setupAuth(app, {
|
|
35
|
+
dataDir: './data-example',
|
|
36
|
+
exposeErrors: true, // Always show details for this demo
|
|
37
|
+
logger: new MyCustomLogger(), // Plug in your implementation
|
|
38
|
+
config: { domain: 'localhost' }
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Standard mount point
|
|
42
|
+
app.use('/api/v1/auth', authRouter);
|
|
43
|
+
|
|
44
|
+
// Test route to trigger an error
|
|
45
|
+
app.get('/test-error', (req, res, next) => {
|
|
46
|
+
next(new Error('This is a simulated system error'));
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Import the error logger middleware last!
|
|
50
|
+
import { authErrorLogger } from '../src/index.js';
|
|
51
|
+
app.use(authErrorLogger);
|
|
52
|
+
|
|
53
|
+
app.get('/', (req, res) => {
|
|
54
|
+
res.send(`
|
|
55
|
+
<!DOCTYPE html>
|
|
56
|
+
<html>
|
|
57
|
+
<head><title>Auth SDK Demo - Custom Logger</title></head>
|
|
58
|
+
<body style="font-family: sans-serif; padding: 2rem; background: #fafafa;">
|
|
59
|
+
<h1>Auth SDK Demo: Custom Logger</h1>
|
|
60
|
+
<p>This page demonstrates how the SDK interacts with a custom server-side logger.</p>
|
|
61
|
+
|
|
62
|
+
<div id="controls">
|
|
63
|
+
<button id="triggerErrorBtn">1. Trigger Server Error (500)</button>
|
|
64
|
+
<button id="reportBtn">2. Manually Report SDK Error</button>
|
|
65
|
+
</div>
|
|
66
|
+
<hr>
|
|
67
|
+
<div id="output" style="background: #333; color: #fff; padding: 1rem; border-radius: 4px; font-family: monospace; min-height: 100px; white-space: pre-wrap;">Console Output...</div>
|
|
68
|
+
<p><small>Check the <b>Node.js terminal</b> to see the [MY-APP-ERROR] logs!</small></p>
|
|
69
|
+
|
|
70
|
+
<script type="module">
|
|
71
|
+
import { AuthClient } from '/auth-sdk.js';
|
|
72
|
+
const auth = new AuthClient();
|
|
73
|
+
|
|
74
|
+
const log = (msg) => {
|
|
75
|
+
document.getElementById('output').innerText += '\\n> ' + msg;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
document.getElementById('triggerErrorBtn').onclick = async () => {
|
|
79
|
+
try {
|
|
80
|
+
log('Fetching /test-error...');
|
|
81
|
+
const res = await fetch('/test-error');
|
|
82
|
+
const data = await res.json();
|
|
83
|
+
log('Server matched error: ' + JSON.stringify(data));
|
|
84
|
+
} catch (e) { log('Fetch error: ' + e.message); }
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
document.getElementById('reportBtn').onclick = async () => {
|
|
88
|
+
try {
|
|
89
|
+
log('Reporting client-side error to server...');
|
|
90
|
+
await auth.reportError(new Error('Something went wrong on the client!'), {
|
|
91
|
+
browser: navigator.userAgent
|
|
92
|
+
});
|
|
93
|
+
log('Error reported successfully!');
|
|
94
|
+
} catch (e) { log('Report Error: ' + e.message); }
|
|
95
|
+
};
|
|
96
|
+
</script>
|
|
97
|
+
</body>
|
|
98
|
+
</html>
|
|
99
|
+
`);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const PORT = 3000;
|
|
103
|
+
app.listen(PORT, () => {
|
|
104
|
+
console.log(`Example 05 running at http://localhost:${PORT}`);
|
|
105
|
+
});
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example 06: Password Reset
|
|
3
|
+
*
|
|
4
|
+
* Demonstrates the full flow of requesting a reset token and resetting a password.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import express from 'express';
|
|
8
|
+
import session from 'express-session';
|
|
9
|
+
import {
|
|
10
|
+
setupAuth,
|
|
11
|
+
authRouter,
|
|
12
|
+
SQLiteSessionStore,
|
|
13
|
+
authDb
|
|
14
|
+
} from '../src/index.js';
|
|
15
|
+
|
|
16
|
+
const app = express();
|
|
17
|
+
app.use(express.json());
|
|
18
|
+
|
|
19
|
+
setupAuth(app, {
|
|
20
|
+
dataDir: './data-example-reset',
|
|
21
|
+
exposeErrors: true,
|
|
22
|
+
config: { domain: 'localhost' }
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
app.use(session({
|
|
26
|
+
secret: 'reset-secret',
|
|
27
|
+
store: new SQLiteSessionStore(),
|
|
28
|
+
resave: false,
|
|
29
|
+
saveUninitialized: false,
|
|
30
|
+
cookie: { secure: false }
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
// Standard mount point
|
|
34
|
+
app.use('/api/v1/auth', authRouter);
|
|
35
|
+
|
|
36
|
+
app.get('/', (req, res) => {
|
|
37
|
+
res.send(`
|
|
38
|
+
<!DOCTYPE html>
|
|
39
|
+
<html>
|
|
40
|
+
<head><title>Auth SDK Demo - Password Reset</title></head>
|
|
41
|
+
<body style="font-family: sans-serif; padding: 2rem; background: #f0fdf4;">
|
|
42
|
+
<h1>Auth SDK Demo: Password Reset</h1>
|
|
43
|
+
<p>This demo shows how to request a reset token and use it to change a password.</p>
|
|
44
|
+
|
|
45
|
+
<div id="controls">
|
|
46
|
+
<button id="regBtn">1. Register User (user_reset)</button>
|
|
47
|
+
<button id="forgotBtn">2. Forgot Password</button>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
<div id="resetArea" style="margin-top: 2rem; display: none; background: #fff; padding: 1.5rem; border: 1px solid #dcfce7; border-radius: 8px;">
|
|
51
|
+
<h3>Reset Password</h3>
|
|
52
|
+
<p>A token was generated in the database (simulating an email).</p>
|
|
53
|
+
<input type="text" id="tokenIn" placeholder="Enter Token">
|
|
54
|
+
<input type="password" id="passIn" placeholder="New Password">
|
|
55
|
+
<button id="resetBtn">3. Reset Password</button>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<hr>
|
|
59
|
+
<div id="output" style="background: #333; color: #fff; padding: 1rem; border-radius: 4px; font-family: monospace; min-height: 100px; white-space: pre-wrap;">Console Output...</div>
|
|
60
|
+
|
|
61
|
+
<script type="module">
|
|
62
|
+
import { AuthClient } from '/auth-sdk.js';
|
|
63
|
+
const auth = new AuthClient();
|
|
64
|
+
|
|
65
|
+
const log = (msg) => {
|
|
66
|
+
document.getElementById('output').innerText += '\\n> ' + msg;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
document.getElementById('regBtn').onclick = async () => {
|
|
70
|
+
try {
|
|
71
|
+
await auth.register('user_reset', 'reset@example.com', 'oldpassword');
|
|
72
|
+
log('User registered with password: oldpassword');
|
|
73
|
+
} catch (e) { log('Error: ' + e.message); }
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
document.getElementById('forgotBtn').onclick = async () => {
|
|
77
|
+
try {
|
|
78
|
+
log('Requesting reset for user_reset...');
|
|
79
|
+
const res = await auth.forgotPassword('user_reset');
|
|
80
|
+
log('Request success! Check the server logs (or DB) for the token.');
|
|
81
|
+
document.getElementById('resetArea').style.display = 'block';
|
|
82
|
+
} catch (e) { log('Error: ' + e.message); }
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
document.getElementById('resetBtn').onclick = async () => {
|
|
86
|
+
const token = document.getElementById('tokenIn').value;
|
|
87
|
+
const pass = document.getElementById('passIn').value;
|
|
88
|
+
try {
|
|
89
|
+
await auth.resetPassword(token, pass);
|
|
90
|
+
log('Password successfully reset! You can now login with the new password.');
|
|
91
|
+
document.getElementById('resetArea').style.display = 'none';
|
|
92
|
+
} catch (e) { log('Error: ' + e.message); }
|
|
93
|
+
};
|
|
94
|
+
</script>
|
|
95
|
+
</body>
|
|
96
|
+
</html>
|
|
97
|
+
`);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const PORT = 3000;
|
|
101
|
+
app.listen(PORT, () => {
|
|
102
|
+
console.log(`Example 06 running at http://localhost:${PORT}`);
|
|
103
|
+
console.log(`NOTE: In a real app, you would email the token to the user.`);
|
|
104
|
+
});
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example 08: External Database Linking
|
|
3
|
+
*
|
|
4
|
+
* Demonstrates the "contained identity" model:
|
|
5
|
+
* Auth server handles identity, while your app database handles profile data.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import express from 'express';
|
|
9
|
+
import session from 'express-session';
|
|
10
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
11
|
+
import {
|
|
12
|
+
setupAuth,
|
|
13
|
+
authRouter,
|
|
14
|
+
SQLiteSessionStore,
|
|
15
|
+
requireAuth,
|
|
16
|
+
AuthClient
|
|
17
|
+
} from '../src/index.js';
|
|
18
|
+
|
|
19
|
+
const app = express();
|
|
20
|
+
app.use(express.json());
|
|
21
|
+
|
|
22
|
+
// ─── 1. SETUP AUTH SERVER ────────────────────────────────────────────────────
|
|
23
|
+
setupAuth(app, {
|
|
24
|
+
dataDir: './data-example-external',
|
|
25
|
+
config: { domain: 'localhost' }
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
app.use(session({
|
|
29
|
+
secret: 'external-db-secret',
|
|
30
|
+
store: new SQLiteSessionStore(),
|
|
31
|
+
resave: false,
|
|
32
|
+
saveUninitialized: false,
|
|
33
|
+
cookie: { secure: false }
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
// Standard mount point
|
|
37
|
+
app.use('/api/v1/auth', authRouter);
|
|
38
|
+
|
|
39
|
+
// ─── 2. SETUP APPLICATION DATABASE (Managed by Developer) ────────────────────
|
|
40
|
+
const appDataDb = new DatabaseSync('./data-example-external/app_data.db');
|
|
41
|
+
appDataDb.exec(`
|
|
42
|
+
CREATE TABLE IF NOT EXISTS user_profiles (
|
|
43
|
+
user_id TEXT PRIMARY KEY,
|
|
44
|
+
bio TEXT,
|
|
45
|
+
website TEXT,
|
|
46
|
+
theme TEXT DEFAULT 'light'
|
|
47
|
+
)
|
|
48
|
+
`);
|
|
49
|
+
|
|
50
|
+
const getProfile = appDataDb.prepare('SELECT * FROM user_profiles WHERE user_id = ?');
|
|
51
|
+
const upsertProfile = appDataDb.prepare(`
|
|
52
|
+
INSERT INTO user_profiles (user_id, bio, website)
|
|
53
|
+
VALUES (?, ?, ?)
|
|
54
|
+
ON CONFLICT(user_id) DO UPDATE SET bio=excluded.bio, website=excluded.website
|
|
55
|
+
`);
|
|
56
|
+
|
|
57
|
+
// ─── 3. APP ROUTES (Combining Identity + Data) ───────────────────────────────
|
|
58
|
+
|
|
59
|
+
// Get merged profile (Auth Identity + App Data)
|
|
60
|
+
app.get('/api/app/profile', requireAuth, async (req, res) => {
|
|
61
|
+
// req.userId comes from the Auth Server middleware
|
|
62
|
+
const profile = getProfile.get(req.userId) || { bio: '', website: '' };
|
|
63
|
+
|
|
64
|
+
res.json({
|
|
65
|
+
userId: req.userId,
|
|
66
|
+
username: req.session.user.username, // From session
|
|
67
|
+
...profile
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Update app-specific data
|
|
72
|
+
app.post('/api/app/profile', requireAuth, (req, res) => {
|
|
73
|
+
const { bio, website } = req.body;
|
|
74
|
+
upsertProfile.run(req.userId, bio, website);
|
|
75
|
+
res.json({ success: true });
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
app.get('/', (req, res) => {
|
|
79
|
+
res.send(`
|
|
80
|
+
<!DOCTYPE html>
|
|
81
|
+
<html>
|
|
82
|
+
<head><title>Auth SDK Demo - External DB</title></head>
|
|
83
|
+
<body style="font-family: sans-serif; padding: 2rem; background: #f8fafc;">
|
|
84
|
+
<h1>Auth SDK Demo: External DB Linking</h1>
|
|
85
|
+
<p>This demo shows how to keep Identity (Auth Server) separate from Application Data.</p>
|
|
86
|
+
|
|
87
|
+
<div id="status">Checking Auth...</div>
|
|
88
|
+
<hr>
|
|
89
|
+
|
|
90
|
+
<div id="profileSection" style="display:none; background:white; padding:1.5 val rem; border:1px solid #e2e8f0; border-radius:8px; max-width:400px;">
|
|
91
|
+
<h3>Your Profile (App Data)</h3>
|
|
92
|
+
<p>User ID: <span id="uidDisplay"></span></p>
|
|
93
|
+
<div style="margin-bottom:1rem">
|
|
94
|
+
<label>Bio:</label><br>
|
|
95
|
+
<textarea id="bioInput" style="width:100%"></textarea>
|
|
96
|
+
</div>
|
|
97
|
+
<div style="margin-bottom:1rem">
|
|
98
|
+
<label>Website:</label><br>
|
|
99
|
+
<input type="text" id="webInput" style="width:100%">
|
|
100
|
+
</div>
|
|
101
|
+
<button id="saveBtn">Save App Data</button>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
<div id="authSection" style="display:none;">
|
|
105
|
+
<button id="loginBtn">Login / Register</button>
|
|
106
|
+
</div>
|
|
107
|
+
|
|
108
|
+
<script type="module">
|
|
109
|
+
import { AuthClient } from '/auth-sdk.js';
|
|
110
|
+
const auth = new AuthClient();
|
|
111
|
+
|
|
112
|
+
async function init() {
|
|
113
|
+
const status = await auth.getStatus();
|
|
114
|
+
if (status.authenticated) {
|
|
115
|
+
document.getElementById('profileSection').style.display = 'block';
|
|
116
|
+
document.getElementById('uidDisplay').innerText = status.userId;
|
|
117
|
+
|
|
118
|
+
// Fetch app data
|
|
119
|
+
const res = await fetch('/api/app/profile');
|
|
120
|
+
const profile = await res.json();
|
|
121
|
+
document.getElementById('bioInput').value = profile.bio;
|
|
122
|
+
document.getElementById('webInput').value = profile.website;
|
|
123
|
+
document.getElementById('status').innerText = 'Logged in as ' + status.username;
|
|
124
|
+
} else {
|
|
125
|
+
document.getElementById('authSection').style.display = 'block';
|
|
126
|
+
document.getElementById('status').innerText = 'Not logged in';
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
document.getElementById('loginBtn').onclick = async () => {
|
|
131
|
+
const u = 'db_user_' + Math.floor(Math.random()*1000);
|
|
132
|
+
await auth.register(u, u+'@example.com', 'password123').catch(()=>{});
|
|
133
|
+
await auth.login(u, 'password123');
|
|
134
|
+
location.reload();
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
document.getElementById('saveBtn').onclick = async () => {
|
|
138
|
+
const bio = document.getElementById('bioInput').value;
|
|
139
|
+
const website = document.getElementById('webInput').value;
|
|
140
|
+
await fetch('/api/app/profile', {
|
|
141
|
+
method: 'POST',
|
|
142
|
+
headers: {'Content-Type': 'application/json'},
|
|
143
|
+
body: JSON.stringify({ bio, website })
|
|
144
|
+
});
|
|
145
|
+
alert('Saved to App Database!');
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
init();
|
|
149
|
+
</script>
|
|
150
|
+
</body>
|
|
151
|
+
</html>
|
|
152
|
+
`);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const PORT = 3000;
|
|
156
|
+
app.listen(PORT, () => {
|
|
157
|
+
console.log(`Example 08 running at http://localhost:${PORT}`);
|
|
158
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Auth Server Library Examples
|
|
2
|
+
|
|
3
|
+
This directory contains standalone, well-documented reference implementations for each major feature of the library. These examples are designed to be read and run in isolation.
|
|
4
|
+
|
|
5
|
+
## 🚀 How to Run
|
|
6
|
+
Most examples require an Express environment. To run an example:
|
|
7
|
+
|
|
8
|
+
1. `npm install` (if you haven't already)
|
|
9
|
+
2. `node examples/01-basic-setup.js`
|
|
10
|
+
|
|
11
|
+
## 📖 Available Examples
|
|
12
|
+
|
|
13
|
+
### 01. Basic Setup
|
|
14
|
+
**File**: `01-basic-setup.js`
|
|
15
|
+
The "Hello World" of the library. Shows how to initialize `setupAuth`, configure standard Express sessions, and protect a basic route.
|
|
16
|
+
|
|
17
|
+
### 02. Passkeys (WebAuthn)
|
|
18
|
+
**File**: `02-passkeys.js`
|
|
19
|
+
Focuses on the passwordless experience. Shows how to mount the `passkeysRouter` and integrate the `AuthClient` ceremonies for registration and login.
|
|
20
|
+
|
|
21
|
+
### 03. API Keys
|
|
22
|
+
**File**: `03-api-keys.js`
|
|
23
|
+
Demonstrates programmatic access. Create machine-to-machine keys with granular permissions (`action:read`, `action:write`) and verify them using the `requireApiKey` middleware.
|
|
24
|
+
|
|
25
|
+
### 04. TOTP 2FA lifecycle
|
|
26
|
+
**File**: `04-totp-setup.js`
|
|
27
|
+
Covers the setup and verification of Google Authenticator-style 2FA. Includes an example of `requireFreshAuth` for sensitive actions.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
> [!TIP]
|
|
32
|
+
> **Prototyping**: You can copy these files directly into your project as a starting point. All examples use local SQLite databases (`./data-example`) by default.
|
package/openapi.yaml
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
openapi: 3.0.3
|
|
2
|
+
info:
|
|
3
|
+
title: Auth Server API
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
description: |
|
|
6
|
+
API for authentication, user management, passkeys, and API key access.
|
|
7
|
+
All endpoints require either session or API key authentication.
|
|
8
|
+
servers:
|
|
9
|
+
- url: /api/v1
|
|
10
|
+
components:
|
|
11
|
+
securitySchemes:
|
|
12
|
+
bearerAuth:
|
|
13
|
+
type: http
|
|
14
|
+
scheme: bearer
|
|
15
|
+
bearerFormat: API_KEY
|
|
16
|
+
cookieAuth:
|
|
17
|
+
type: apiKey
|
|
18
|
+
in: cookie
|
|
19
|
+
name: connect.sid
|
|
20
|
+
paths:
|
|
21
|
+
/user/me:
|
|
22
|
+
get:
|
|
23
|
+
summary: Get user profile
|
|
24
|
+
security:
|
|
25
|
+
- cookieAuth: []
|
|
26
|
+
responses:
|
|
27
|
+
'200':
|
|
28
|
+
description: User profile
|
|
29
|
+
content:
|
|
30
|
+
application/json:
|
|
31
|
+
schema:
|
|
32
|
+
type: object
|
|
33
|
+
properties:
|
|
34
|
+
id:
|
|
35
|
+
type: string
|
|
36
|
+
username:
|
|
37
|
+
type: string
|
|
38
|
+
email:
|
|
39
|
+
type: string
|
|
40
|
+
profile:
|
|
41
|
+
type: object
|
|
42
|
+
'401':
|
|
43
|
+
description: Not authenticated
|
|
44
|
+
content:
|
|
45
|
+
application/json:
|
|
46
|
+
schema:
|
|
47
|
+
type: object
|
|
48
|
+
properties:
|
|
49
|
+
error:
|
|
50
|
+
type: string
|
|
51
|
+
/user/keys:
|
|
52
|
+
get:
|
|
53
|
+
summary: List API keys
|
|
54
|
+
security:
|
|
55
|
+
- cookieAuth: []
|
|
56
|
+
responses:
|
|
57
|
+
'200':
|
|
58
|
+
description: List of API keys
|
|
59
|
+
content:
|
|
60
|
+
application/json:
|
|
61
|
+
schema:
|
|
62
|
+
type: object
|
|
63
|
+
properties:
|
|
64
|
+
keys:
|
|
65
|
+
type: array
|
|
66
|
+
items:
|
|
67
|
+
type: object
|
|
68
|
+
properties:
|
|
69
|
+
id:
|
|
70
|
+
type: string
|
|
71
|
+
name:
|
|
72
|
+
type: string
|
|
73
|
+
permissions:
|
|
74
|
+
type: array
|
|
75
|
+
items:
|
|
76
|
+
type: string
|
|
77
|
+
created_at:
|
|
78
|
+
type: integer
|
|
79
|
+
last_used:
|
|
80
|
+
type: integer
|
|
81
|
+
'401':
|
|
82
|
+
description: Not authenticated
|
|
83
|
+
content:
|
|
84
|
+
application/json:
|
|
85
|
+
schema:
|
|
86
|
+
type: object
|
|
87
|
+
properties:
|
|
88
|
+
error:
|
|
89
|
+
type: string
|
|
90
|
+
post:
|
|
91
|
+
summary: Create API key
|
|
92
|
+
security:
|
|
93
|
+
- cookieAuth: []
|
|
94
|
+
requestBody:
|
|
95
|
+
required: true
|
|
96
|
+
content:
|
|
97
|
+
application/json:
|
|
98
|
+
schema:
|
|
99
|
+
type: object
|
|
100
|
+
properties:
|
|
101
|
+
name:
|
|
102
|
+
type: string
|
|
103
|
+
permissions:
|
|
104
|
+
type: array
|
|
105
|
+
items:
|
|
106
|
+
type: string
|
|
107
|
+
responses:
|
|
108
|
+
'201':
|
|
109
|
+
description: Created
|
|
110
|
+
content:
|
|
111
|
+
application/json:
|
|
112
|
+
schema:
|
|
113
|
+
type: object
|
|
114
|
+
properties:
|
|
115
|
+
success:
|
|
116
|
+
type: boolean
|
|
117
|
+
key:
|
|
118
|
+
type: string
|
|
119
|
+
metadata:
|
|
120
|
+
type: object
|
|
121
|
+
'400':
|
|
122
|
+
description: Bad Request
|
|
123
|
+
content:
|
|
124
|
+
application/json:
|
|
125
|
+
schema:
|
|
126
|
+
type: object
|
|
127
|
+
properties:
|
|
128
|
+
error:
|
|
129
|
+
type: string
|
|
130
|
+
delete:
|
|
131
|
+
summary: Delete API key
|
|
132
|
+
security:
|
|
133
|
+
- cookieAuth: []
|
|
134
|
+
parameters:
|
|
135
|
+
- in: path
|
|
136
|
+
name: id
|
|
137
|
+
required: true
|
|
138
|
+
schema:
|
|
139
|
+
type: string
|
|
140
|
+
responses:
|
|
141
|
+
'200':
|
|
142
|
+
description: Deleted
|
|
143
|
+
content:
|
|
144
|
+
application/json:
|
|
145
|
+
schema:
|
|
146
|
+
type: object
|
|
147
|
+
properties:
|
|
148
|
+
success:
|
|
149
|
+
type: boolean
|
|
150
|
+
'404':
|
|
151
|
+
description: Not found
|
|
152
|
+
content:
|
|
153
|
+
application/json:
|
|
154
|
+
schema:
|
|
155
|
+
type: object
|
|
156
|
+
properties:
|
|
157
|
+
error:
|
|
158
|
+
type: string
|
|
159
|
+
/auth/register:
|
|
160
|
+
post:
|
|
161
|
+
summary: Register a new user
|
|
162
|
+
requestBody:
|
|
163
|
+
required: true
|
|
164
|
+
content:
|
|
165
|
+
application/json:
|
|
166
|
+
schema:
|
|
167
|
+
type: object
|
|
168
|
+
properties:
|
|
169
|
+
username:
|
|
170
|
+
type: string
|
|
171
|
+
email:
|
|
172
|
+
type: string
|
|
173
|
+
password:
|
|
174
|
+
type: string
|
|
175
|
+
responses:
|
|
176
|
+
'201':
|
|
177
|
+
description: Registered
|
|
178
|
+
content:
|
|
179
|
+
application/json:
|
|
180
|
+
schema:
|
|
181
|
+
type: object
|
|
182
|
+
properties:
|
|
183
|
+
userId:
|
|
184
|
+
type: string
|
|
185
|
+
username:
|
|
186
|
+
type: string
|
|
187
|
+
message:
|
|
188
|
+
type: string
|
|
189
|
+
user:
|
|
190
|
+
type: object
|
|
191
|
+
'400':
|
|
192
|
+
description: Missing or invalid input
|
|
193
|
+
content:
|
|
194
|
+
application/json:
|
|
195
|
+
schema:
|
|
196
|
+
type: object
|
|
197
|
+
properties:
|
|
198
|
+
error:
|
|
199
|
+
type: string
|
|
200
|
+
'409':
|
|
201
|
+
description: Username or email taken
|
|
202
|
+
content:
|
|
203
|
+
application/json:
|
|
204
|
+
schema:
|
|
205
|
+
type: object
|
|
206
|
+
properties:
|
|
207
|
+
error:
|
|
208
|
+
type: string
|
|
209
|
+
/auth/login:
|
|
210
|
+
post:
|
|
211
|
+
summary: Login with username and password
|
|
212
|
+
requestBody:
|
|
213
|
+
required: true
|
|
214
|
+
content:
|
|
215
|
+
application/json:
|
|
216
|
+
schema:
|
|
217
|
+
type: object
|
|
218
|
+
properties:
|
|
219
|
+
username:
|
|
220
|
+
type: string
|
|
221
|
+
password:
|
|
222
|
+
type: string
|
|
223
|
+
responses:
|
|
224
|
+
'200':
|
|
225
|
+
description: Login successful
|
|
226
|
+
content:
|
|
227
|
+
application/json:
|
|
228
|
+
schema:
|
|
229
|
+
type: object
|
|
230
|
+
properties:
|
|
231
|
+
userId:
|
|
232
|
+
type: string
|
|
233
|
+
username:
|
|
234
|
+
type: string
|
|
235
|
+
user:
|
|
236
|
+
type: object
|
|
237
|
+
'400':
|
|
238
|
+
description: Missing credentials
|
|
239
|
+
content:
|
|
240
|
+
application/json:
|
|
241
|
+
schema:
|
|
242
|
+
type: object
|
|
243
|
+
properties:
|
|
244
|
+
error:
|
|
245
|
+
type: string
|
|
246
|
+
'401':
|
|
247
|
+
description: Invalid credentials
|
|
248
|
+
content:
|
|
249
|
+
application/json:
|
|
250
|
+
schema:
|
|
251
|
+
type: object
|
|
252
|
+
properties:
|
|
253
|
+
error:
|
|
254
|
+
type: string
|
|
255
|
+
'403':
|
|
256
|
+
description: Account locked
|
|
257
|
+
content:
|
|
258
|
+
application/json:
|
|
259
|
+
schema:
|
|
260
|
+
type: object
|
|
261
|
+
properties:
|
|
262
|
+
error:
|
|
263
|
+
type: string
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@javagt/express-easy-auth",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.js",
|
|
9
|
+
"./client": "./src/client.js"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
13
|
+
"start": "node demo/server.js",
|
|
14
|
+
"dev": "node demo/server.js"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [],
|
|
17
|
+
"author": "",
|
|
18
|
+
"license": "ISC",
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@simplewebauthn/server": "^13.3.0",
|
|
24
|
+
"bcrypt": "^6.0.0",
|
|
25
|
+
"cookie-parser": "^1.4.7",
|
|
26
|
+
"cors": "^2.8.6",
|
|
27
|
+
"dotenv": "^17.4.1",
|
|
28
|
+
"express": "^5.2.1",
|
|
29
|
+
"express-session": "^1.19.0",
|
|
30
|
+
"helmet": "^8.1.0",
|
|
31
|
+
"otplib": "^13.4.0",
|
|
32
|
+
"qrcode": "^1.5.4",
|
|
33
|
+
"uuid": "^13.0.0"
|
|
34
|
+
}
|
|
35
|
+
}
|