chadstart 1.0.1 → 1.0.2
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 +20 -0
- package/chadstart.example.yml +52 -0
- package/chadstart.schema.json +62 -0
- package/core/entity-engine.js +1 -0
- package/core/oauth.js +263 -0
- package/docs/auth.md +3 -0
- package/docs/oauth.md +869 -0
- package/mkdocs.yml +1 -0
- package/package.json +2 -1
- package/server/express-server.js +2 -0
- package/test/oauth.test.js +259 -0
package/.env.example
CHANGED
|
@@ -53,3 +53,23 @@ TOKEN_SECRET_KEY=replace-with-a-long-random-secret
|
|
|
53
53
|
# 💡 Bugsink (https://www.bugsink.com) is a self-hosted alternative to Sentry
|
|
54
54
|
# that uses the same Sentry SDK — just point SENTRY_DSN at your Bugsink instance.
|
|
55
55
|
# SENTRY_DSN=https://xxxxx@oXXXXX.ingest.sentry.io/XXXXXXX
|
|
56
|
+
|
|
57
|
+
# Optional: OAuth / Social Login (powered by grant)
|
|
58
|
+
# For each provider, set OAUTH_<PROVIDER>_KEY and OAUTH_<PROVIDER>_SECRET.
|
|
59
|
+
# Provider names must be uppercase (e.g. GOOGLE, GITHUB, FACEBOOK).
|
|
60
|
+
# See docs/oauth.md for the full list of 200+ supported providers.
|
|
61
|
+
#
|
|
62
|
+
# OAUTH_GOOGLE_KEY=your-google-client-id
|
|
63
|
+
# OAUTH_GOOGLE_SECRET=your-google-client-secret
|
|
64
|
+
# OAUTH_GITHUB_KEY=your-github-client-id
|
|
65
|
+
# OAUTH_GITHUB_SECRET=your-github-client-secret
|
|
66
|
+
# OAUTH_FACEBOOK_KEY=your-facebook-app-id
|
|
67
|
+
# OAUTH_FACEBOOK_SECRET=your-facebook-app-secret
|
|
68
|
+
# OAUTH_DISCORD_KEY=your-discord-client-id
|
|
69
|
+
# OAUTH_DISCORD_SECRET=your-discord-client-secret
|
|
70
|
+
# OAUTH_APPLE_KEY=your-apple-client-id
|
|
71
|
+
# OAUTH_APPLE_SECRET=your-apple-client-secret
|
|
72
|
+
# OAUTH_MICROSOFT_KEY=your-microsoft-client-id
|
|
73
|
+
# OAUTH_MICROSOFT_SECRET=your-microsoft-client-secret
|
|
74
|
+
# OAUTH_TWITTER_KEY=your-twitter-api-key
|
|
75
|
+
# OAUTH_TWITTER_SECRET=your-twitter-api-secret
|
package/chadstart.example.yml
CHANGED
|
@@ -414,3 +414,55 @@ sentry:
|
|
|
414
414
|
environment: production # Label sent to Sentry. Defaults to NODE_ENV.
|
|
415
415
|
tracesSampleRate: 1.0 # Fraction of transactions to sample (0.0–1.0)
|
|
416
416
|
debug: false # Enable Sentry SDK debug logging
|
|
417
|
+
|
|
418
|
+
# ── OAuth / Social Login ─────────────────────────────────────────────────────
|
|
419
|
+
# Powered by the "grant" library — supports 200+ OAuth providers.
|
|
420
|
+
# Secrets (client keys / secrets) MUST be set via environment variables:
|
|
421
|
+
# OAUTH_<PROVIDER>_KEY — client / app ID
|
|
422
|
+
# OAUTH_<PROVIDER>_SECRET — client / app secret
|
|
423
|
+
# See docs/oauth.md for the full provider list and setup guides.
|
|
424
|
+
|
|
425
|
+
oauth:
|
|
426
|
+
# Which authenticable entity to create/find users in (default: first authenticable entity).
|
|
427
|
+
entity: User
|
|
428
|
+
|
|
429
|
+
# Where to redirect after successful login. The JWT token is appended as ?token=...
|
|
430
|
+
# If omitted, the callback returns JSON instead.
|
|
431
|
+
successRedirect: /login?success=true
|
|
432
|
+
|
|
433
|
+
# Where to redirect on error. The error message is appended as ?error=...
|
|
434
|
+
errorRedirect: /login?error=true
|
|
435
|
+
|
|
436
|
+
# Default settings applied to all providers.
|
|
437
|
+
defaults:
|
|
438
|
+
transport: querystring
|
|
439
|
+
|
|
440
|
+
# Configure each provider you want to support.
|
|
441
|
+
# The provider names must match grant's provider names (lowercase).
|
|
442
|
+
# Full list: https://github.com/simov/grant#200-supported-providers
|
|
443
|
+
providers:
|
|
444
|
+
google:
|
|
445
|
+
scope:
|
|
446
|
+
- openid
|
|
447
|
+
- email
|
|
448
|
+
- profile
|
|
449
|
+
custom_params:
|
|
450
|
+
access_type: offline
|
|
451
|
+
# key and secret via: OAUTH_GOOGLE_KEY, OAUTH_GOOGLE_SECRET
|
|
452
|
+
|
|
453
|
+
github:
|
|
454
|
+
scope:
|
|
455
|
+
- user:email
|
|
456
|
+
# key and secret via: OAUTH_GITHUB_KEY, OAUTH_GITHUB_SECRET
|
|
457
|
+
|
|
458
|
+
# facebook:
|
|
459
|
+
# scope:
|
|
460
|
+
# - email
|
|
461
|
+
# - public_profile
|
|
462
|
+
# key and secret via: OAUTH_FACEBOOK_KEY, OAUTH_FACEBOOK_SECRET
|
|
463
|
+
|
|
464
|
+
# discord:
|
|
465
|
+
# scope:
|
|
466
|
+
# - identify
|
|
467
|
+
# - email
|
|
468
|
+
# key and secret via: OAUTH_DISCORD_KEY, OAUTH_DISCORD_SECRET
|
package/chadstart.schema.json
CHANGED
|
@@ -124,6 +124,43 @@
|
|
|
124
124
|
"description": "Enable Sentry SDK debug logging."
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
|
+
},
|
|
128
|
+
"oauth": {
|
|
129
|
+
"type": "object",
|
|
130
|
+
"description": "OAuth / social login configuration powered by the grant library. Secrets (client keys and secrets) must be supplied via OAUTH_<PROVIDER>_KEY and OAUTH_<PROVIDER>_SECRET environment variables.",
|
|
131
|
+
"additionalProperties": false,
|
|
132
|
+
"properties": {
|
|
133
|
+
"entity": {
|
|
134
|
+
"type": "string",
|
|
135
|
+
"description": "Name of the authenticable entity to use for OAuth users (e.g. 'User'). Defaults to the first authenticable entity."
|
|
136
|
+
},
|
|
137
|
+
"successRedirect": {
|
|
138
|
+
"type": "string",
|
|
139
|
+
"description": "URL to redirect to after successful OAuth login. The JWT token is appended as a ?token= query parameter. If omitted, returns JSON."
|
|
140
|
+
},
|
|
141
|
+
"errorRedirect": {
|
|
142
|
+
"type": "string",
|
|
143
|
+
"description": "URL to redirect to on OAuth error. The error message is appended as an ?error= query parameter. If omitted, returns JSON error."
|
|
144
|
+
},
|
|
145
|
+
"defaults": {
|
|
146
|
+
"type": "object",
|
|
147
|
+
"description": "Default settings applied to all providers (e.g. transport, scope).",
|
|
148
|
+
"properties": {
|
|
149
|
+
"transport": {
|
|
150
|
+
"type": "string",
|
|
151
|
+
"enum": ["querystring", "session"],
|
|
152
|
+
"default": "querystring"
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
"providers": {
|
|
157
|
+
"type": "object",
|
|
158
|
+
"description": "Map of OAuth provider names to their configuration. Provider names must match grant's supported provider list (e.g. google, github, facebook). See https://www.npmjs.com/package/grant for all 200+ supported providers.",
|
|
159
|
+
"additionalProperties": {
|
|
160
|
+
"$ref": "#/$defs/oauthProvider"
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
127
164
|
}
|
|
128
165
|
},
|
|
129
166
|
"$defs": {
|
|
@@ -362,6 +399,31 @@
|
|
|
362
399
|
"limit": { "type": "integer", "description": "Maximum number of requests allowed in the time window." },
|
|
363
400
|
"ttl": { "type": "integer", "description": "Time window in milliseconds." }
|
|
364
401
|
}
|
|
402
|
+
},
|
|
403
|
+
"oauthProvider": {
|
|
404
|
+
"type": "object",
|
|
405
|
+
"description": "Configuration for a single OAuth provider. The key and secret should be set via OAUTH_<PROVIDER>_KEY and OAUTH_<PROVIDER>_SECRET environment variables.",
|
|
406
|
+
"properties": {
|
|
407
|
+
"key": { "type": "string", "description": "OAuth client/app ID. Prefer using OAUTH_<PROVIDER>_KEY env var instead." },
|
|
408
|
+
"secret": { "type": "string", "description": "OAuth client/app secret. Prefer using OAUTH_<PROVIDER>_SECRET env var instead." },
|
|
409
|
+
"scope": {
|
|
410
|
+
"oneOf": [
|
|
411
|
+
{ "type": "string" },
|
|
412
|
+
{ "type": "array", "items": { "type": "string" } }
|
|
413
|
+
],
|
|
414
|
+
"description": "OAuth scopes to request (e.g. 'openid email profile')."
|
|
415
|
+
},
|
|
416
|
+
"callback": { "type": "string", "description": "Custom callback URL path. Defaults to /api/auth/oauth/callback." },
|
|
417
|
+
"custom_params": { "type": "object", "description": "Extra query parameters to send to the authorization URL." },
|
|
418
|
+
"subdomain": { "type": "string", "description": "Subdomain for providers that require one (e.g. Shopify)." },
|
|
419
|
+
"nonce": { "type": "boolean", "description": "Enable nonce generation (required by some OIDC providers)." },
|
|
420
|
+
"pkce": { "type": "boolean", "description": "Enable PKCE (Proof Key for Code Exchange) for enhanced security." },
|
|
421
|
+
"response": {
|
|
422
|
+
"type": "array",
|
|
423
|
+
"items": { "type": "string" },
|
|
424
|
+
"description": "Data to include in the callback (e.g. ['tokens', 'profile'])."
|
|
425
|
+
}
|
|
426
|
+
}
|
|
365
427
|
}
|
|
366
428
|
}
|
|
367
429
|
}
|
package/core/entity-engine.js
CHANGED
|
@@ -155,6 +155,7 @@ function buildCore(config) {
|
|
|
155
155
|
port: parseInt(process.env.CHADSTART_PORT || process.env.PORT || config.port || 3000, 10),
|
|
156
156
|
rateLimits,
|
|
157
157
|
telemetry,
|
|
158
|
+
oauth: config.oauth || null,
|
|
158
159
|
admin: {
|
|
159
160
|
enable_app: adminCfg.enable_app !== false,
|
|
160
161
|
enable_entity: adminCfg.enable_entity !== false,
|
package/core/oauth.js
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* OAuth / Social Login via the "grant" library.
|
|
5
|
+
*
|
|
6
|
+
* When the YAML config contains an `oauth` section, this module:
|
|
7
|
+
* 1. Mounts the Grant middleware at /connect (handles the redirect dance).
|
|
8
|
+
* 2. Registers a callback route at /api/auth/oauth/callback that
|
|
9
|
+
* finds-or-creates the user and returns a JWT.
|
|
10
|
+
*
|
|
11
|
+
* Secrets (client IDs, client secrets) must be supplied via environment
|
|
12
|
+
* variables — never place them in the YAML file.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const crypto = require('crypto');
|
|
16
|
+
const Grant = require('grant').express();
|
|
17
|
+
const rateLimit = require('express-rate-limit');
|
|
18
|
+
const { signToken } = require('./auth');
|
|
19
|
+
const db = require('./db');
|
|
20
|
+
const logger = require('../utils/logger');
|
|
21
|
+
|
|
22
|
+
const oauthLimiter = rateLimit({
|
|
23
|
+
windowMs: 15 * 60 * 1000,
|
|
24
|
+
max: 30,
|
|
25
|
+
standardHeaders: true,
|
|
26
|
+
legacyHeaders: false,
|
|
27
|
+
message: { error: 'Too many OAuth requests, please try again later.' },
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// ─── Provider profile normalisation ─────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Normalise the profile returned by Grant into { email, name, providerId }.
|
|
34
|
+
* Different providers return profile data in different shapes.
|
|
35
|
+
*
|
|
36
|
+
* @param {string} provider Lowercase provider key (e.g. "google").
|
|
37
|
+
* @param {object} profile Raw profile object from the OAuth provider.
|
|
38
|
+
* @returns {{ email: string|null, name: string|null, providerId: string|null }}
|
|
39
|
+
*/
|
|
40
|
+
function normalizeProfile(provider, profile) {
|
|
41
|
+
if (!profile) return { email: null, name: null, providerId: null };
|
|
42
|
+
|
|
43
|
+
const email =
|
|
44
|
+
profile.email ||
|
|
45
|
+
profile.mail ||
|
|
46
|
+
(profile.emails && profile.emails[0] && (profile.emails[0].value || profile.emails[0])) ||
|
|
47
|
+
null;
|
|
48
|
+
const name =
|
|
49
|
+
profile.name ||
|
|
50
|
+
profile.displayName ||
|
|
51
|
+
profile.login ||
|
|
52
|
+
profile.username ||
|
|
53
|
+
(profile.first_name ? `${profile.first_name} ${profile.last_name || ''}`.trim() : null) ||
|
|
54
|
+
null;
|
|
55
|
+
const providerId =
|
|
56
|
+
String(profile.sub || profile.id || profile.user_id || profile.account_id || '');
|
|
57
|
+
|
|
58
|
+
return { email: email || null, name: name || null, providerId: providerId || null };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ─── Grant config builder ───────────────────────────────────────────────────
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Build the Grant configuration object from the parsed YAML `oauth` section.
|
|
65
|
+
*
|
|
66
|
+
* Environment variable overrides (per-provider):
|
|
67
|
+
* OAUTH_<PROVIDER>_KEY – client/app ID
|
|
68
|
+
* OAUTH_<PROVIDER>_SECRET – client/app secret
|
|
69
|
+
*
|
|
70
|
+
* @param {object} oauthConfig The `oauth` block from the YAML config.
|
|
71
|
+
* @param {string} baseUrl The application base URL (e.g. http://localhost:3000).
|
|
72
|
+
* @returns {object} Configuration object accepted by Grant.
|
|
73
|
+
*/
|
|
74
|
+
function buildGrantConfig(oauthConfig, baseUrl) {
|
|
75
|
+
const defaults = oauthConfig.defaults || {};
|
|
76
|
+
const origin = baseUrl.replace(/\/$/, '');
|
|
77
|
+
|
|
78
|
+
const grantConfig = {
|
|
79
|
+
defaults: {
|
|
80
|
+
origin,
|
|
81
|
+
transport: 'querystring',
|
|
82
|
+
prefix: '/connect',
|
|
83
|
+
...defaults,
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const providers = oauthConfig.providers || {};
|
|
88
|
+
for (const [name, cfg] of Object.entries(providers)) {
|
|
89
|
+
const envPrefix = `OAUTH_${name.toUpperCase()}_`;
|
|
90
|
+
const key = process.env[`${envPrefix}KEY`] || cfg.key || '';
|
|
91
|
+
const secret = process.env[`${envPrefix}SECRET`] || cfg.secret || '';
|
|
92
|
+
|
|
93
|
+
grantConfig[name] = {
|
|
94
|
+
...cfg,
|
|
95
|
+
key,
|
|
96
|
+
secret,
|
|
97
|
+
// callback is where Grant redirects after the OAuth dance;
|
|
98
|
+
// we point it at our own API endpoint which issues a JWT.
|
|
99
|
+
callback: cfg.callback || `/api/auth/oauth/callback`,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return grantConfig;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ─── Route registration ─────────────────────────────────────────────────────
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Mount the Grant middleware and register the OAuth callback route.
|
|
110
|
+
*
|
|
111
|
+
* @param {import('express').Application} app
|
|
112
|
+
* @param {object} core Parsed core configuration.
|
|
113
|
+
* @param {Function} emit EventBus emit function.
|
|
114
|
+
*/
|
|
115
|
+
function registerOAuthRoutes(app, core, emit) {
|
|
116
|
+
const oauthConfig = core.oauth;
|
|
117
|
+
if (!oauthConfig || !oauthConfig.providers || Object.keys(oauthConfig.providers).length === 0) {
|
|
118
|
+
return; // OAuth not configured — skip
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const baseUrl = (
|
|
122
|
+
process.env.BASE_URL ||
|
|
123
|
+
`http://localhost:${core.port}`
|
|
124
|
+
).replace(/\/$/, '');
|
|
125
|
+
|
|
126
|
+
const grantConfig = buildGrantConfig(oauthConfig, baseUrl);
|
|
127
|
+
|
|
128
|
+
// Mount grant middleware — handles /connect/:provider and /connect/:provider/callback
|
|
129
|
+
app.use(Grant(grantConfig));
|
|
130
|
+
logger.info(' Mounted OAuth middleware at /connect');
|
|
131
|
+
|
|
132
|
+
// Determine the target authenticable entity for OAuth users.
|
|
133
|
+
// The `oauth.entity` field specifies which entity to use (default: first authenticable entity).
|
|
134
|
+
const targetEntityName = oauthConfig.entity || null;
|
|
135
|
+
const entity =
|
|
136
|
+
(targetEntityName && core.authenticableEntities[targetEntityName]) ||
|
|
137
|
+
Object.values(core.authenticableEntities)[0];
|
|
138
|
+
|
|
139
|
+
if (!entity) {
|
|
140
|
+
logger.warn(' OAuth: no authenticable entity found — OAuth callback will not work.');
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const _emit = typeof emit === 'function' ? emit : () => {};
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* GET /api/auth/oauth/callback
|
|
148
|
+
*
|
|
149
|
+
* Grant redirects here with query-string parameters after the OAuth dance.
|
|
150
|
+
* We extract the access_token / profile, find-or-create the user, and
|
|
151
|
+
* return a JWT (or redirect if `oauth.successRedirect` is set).
|
|
152
|
+
*/
|
|
153
|
+
app.get('/api/auth/oauth/callback', oauthLimiter, async (req, res) => {
|
|
154
|
+
try {
|
|
155
|
+
const { access_token, profile, provider, error } = req.query;
|
|
156
|
+
|
|
157
|
+
if (error) {
|
|
158
|
+
return _handleError(res, oauthConfig, `OAuth error: ${error}`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (!access_token && !profile) {
|
|
162
|
+
return _handleError(res, oauthConfig, 'OAuth callback missing token and profile');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Grant may pass the profile as a JSON string in the querystring
|
|
166
|
+
let parsedProfile = {};
|
|
167
|
+
if (profile) {
|
|
168
|
+
try { parsedProfile = typeof profile === 'string' ? JSON.parse(profile) : profile; } catch { /* ignore */ }
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const providerName = (provider || 'unknown').toLowerCase();
|
|
172
|
+
const { email, name, providerId } = normalizeProfile(providerName, parsedProfile);
|
|
173
|
+
|
|
174
|
+
if (!email && !providerId) {
|
|
175
|
+
return _handleError(res, oauthConfig, 'Could not obtain email or provider ID from OAuth profile');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Find or create the user
|
|
179
|
+
const user = await _findOrCreateOAuthUser(entity, {
|
|
180
|
+
email,
|
|
181
|
+
name,
|
|
182
|
+
provider: providerName,
|
|
183
|
+
providerId,
|
|
184
|
+
accessToken: access_token || null,
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
_emit(`${entity.name}.oauthLogin`, { provider: providerName, userId: user.id });
|
|
188
|
+
|
|
189
|
+
const token = signToken({ id: user.id, entity: entity.name });
|
|
190
|
+
|
|
191
|
+
// Redirect or return JSON based on config
|
|
192
|
+
const successRedirect = oauthConfig.successRedirect;
|
|
193
|
+
if (successRedirect) {
|
|
194
|
+
const sep = successRedirect.includes('?') ? '&' : '?';
|
|
195
|
+
return res.redirect(`${successRedirect}${sep}token=${encodeURIComponent(token)}`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
res.json({ token, user: _omitSensitive(user) });
|
|
199
|
+
} catch (e) {
|
|
200
|
+
logger.error('OAuth callback error:', e.message);
|
|
201
|
+
return _handleError(res, oauthConfig, e.message);
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// List configured providers
|
|
206
|
+
app.get('/api/auth/oauth/providers', oauthLimiter, (_req, res) => {
|
|
207
|
+
const providers = Object.keys(oauthConfig.providers || {});
|
|
208
|
+
res.json({ providers });
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
const providerNames = Object.keys(oauthConfig.providers);
|
|
212
|
+
logger.info(` OAuth providers: ${providerNames.join(', ')}`);
|
|
213
|
+
logger.info(` OAuth callback: /api/auth/oauth/callback`);
|
|
214
|
+
logger.info(` OAuth entity: ${entity.name}`);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Find an existing user by email or create a new one.
|
|
221
|
+
*/
|
|
222
|
+
async function _findOrCreateOAuthUser(entity, { email, name, provider, providerId, accessToken }) {
|
|
223
|
+
const table = entity.tableName;
|
|
224
|
+
|
|
225
|
+
// 1. Try to find by email
|
|
226
|
+
if (email) {
|
|
227
|
+
const existing = await db.findAllSimple(table, { email });
|
|
228
|
+
if (existing.length > 0) return existing[0];
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// 2. Create a new user with a random password (they authenticate via OAuth)
|
|
232
|
+
const newUser = {
|
|
233
|
+
email: email || `${provider}_${providerId}@oauth.local`,
|
|
234
|
+
password: crypto.randomBytes(32).toString('hex'), // random placeholder
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
// Add optional fields if the entity supports them
|
|
238
|
+
const propNames = new Set(entity.properties.map((p) => p.name));
|
|
239
|
+
if (name && propNames.has('name')) newUser.name = name;
|
|
240
|
+
|
|
241
|
+
const bcrypt = require('bcryptjs');
|
|
242
|
+
newUser.password = await bcrypt.hash(newUser.password, 10);
|
|
243
|
+
|
|
244
|
+
const created = await db.create(table, newUser);
|
|
245
|
+
return created;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function _omitSensitive(user) {
|
|
249
|
+
if (!user) return null;
|
|
250
|
+
const { password: _, ...rest } = user;
|
|
251
|
+
return rest;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function _handleError(res, oauthConfig, message) {
|
|
255
|
+
const errorRedirect = oauthConfig.errorRedirect;
|
|
256
|
+
if (errorRedirect) {
|
|
257
|
+
const sep = errorRedirect.includes('?') ? '&' : '?';
|
|
258
|
+
return res.redirect(`${errorRedirect}${sep}error=${encodeURIComponent(message)}`);
|
|
259
|
+
}
|
|
260
|
+
return res.status(400).json({ error: message });
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
module.exports = { registerOAuthRoutes, buildGrantConfig, normalizeProfile };
|
package/docs/auth.md
CHANGED
|
@@ -42,6 +42,9 @@ entities:
|
|
|
42
42
|
|
|
43
43
|
Authenticable entities have 2 extra properties that are used as credentials to log in: `email` and `password`. You do not need to specify them.The `email` property expects a unique valid emails and the `password` property is automatically hashed using _bcryt_ with 10 salt rounds.
|
|
44
44
|
|
|
45
|
+
!!! tip "Social Login"
|
|
46
|
+
Want users to log in with Google, GitHub, Discord, or other OAuth providers? See the [OAuth / Social Login](./oauth.md) guide.
|
|
47
|
+
|
|
45
48
|
## Actions
|
|
46
49
|
|
|
47
50
|
### Login
|