@xeplr/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/LICENSE +21 -0
- package/bin/migrate.js +95 -0
- package/bin/server.js +35 -0
- package/index.js +165 -0
- package/lib/accessMiddleware.js +79 -0
- package/lib/accessService.js +188 -0
- package/lib/adminRouter.js +387 -0
- package/lib/authHelper.js +63 -0
- package/lib/authMiddleware.js +31 -0
- package/lib/authRouter.js +192 -0
- package/lib/authService.js +194 -0
- package/lib/sessionService.js +210 -0
- package/migrations/0001_users.js +22 -0
- package/migrations/0002_menus.js +16 -0
- package/migrations/0003_apis.js +16 -0
- package/migrations/0004_uiPages.js +16 -0
- package/migrations/0005_uiElements.js +16 -0
- package/migrations/0006_roles.js +15 -0
- package/migrations/0007_apisRolesMapping.js +16 -0
- package/migrations/0008_uiPagesRolesMapping.js +16 -0
- package/migrations/0009_uiElementsRolesMapping.js +16 -0
- package/migrations/0010_menuRolesMapping.js +16 -0
- package/migrations/0011_userRolesMapping.js +16 -0
- package/migrations/0012_users_add_reset_token.js +13 -0
- package/migrations/0013_add_isPublic.js +27 -0
- package/migrations/0014_users_add_activation_token.js +13 -0
- package/migrations/0015_add_mt_columns.js +41 -0
- package/migrations/0016_tenants.js +23 -0
- package/migrations/0017_userTenantsMapping.js +20 -0
- package/models/Api.js +45 -0
- package/models/ApisRolesMapping.js +29 -0
- package/models/Menu.js +45 -0
- package/models/MenuRolesMapping.js +29 -0
- package/models/Role.js +89 -0
- package/models/Tenant.js +63 -0
- package/models/UiElement.js +44 -0
- package/models/UiElementsRolesMapping.js +29 -0
- package/models/UiPage.js +45 -0
- package/models/UiPagesRolesMapping.js +29 -0
- package/models/User.js +76 -0
- package/models/UserRolesMapping.js +29 -0
- package/models/UserTenantsMapping.js +30 -0
- package/models/index.js +29 -0
- package/package.json +31 -0
- package/seeds/001_roles.js +26 -0
- package/seeds/002_apis.js +70 -0
- package/seeds/003_pages.js +41 -0
- package/seeds/004_elements.js +46 -0
- package/seeds/005_menus.js +35 -0
- package/seeds/006_default_tenant.js +50 -0
- package/seeds/zzz_admin_access.js +49 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
const crypto = require('crypto');
|
|
2
|
+
const { sendEmail, configureEmail, formatDbDateTime } = require('@xeplr/utils');
|
|
3
|
+
const { normalizeEmail } = require('@xeplr/utils/isomorphic');
|
|
4
|
+
const { generateId, hashPassword, comparePassword } = require('./authHelper');
|
|
5
|
+
const User = require('../models/User');
|
|
6
|
+
const { getUserAccess, clearUserAccess } = require('./accessService');
|
|
7
|
+
const { createSession, destroyAllUserSessions } = require('./sessionService');
|
|
8
|
+
|
|
9
|
+
let _activationBaseUrl = null;
|
|
10
|
+
|
|
11
|
+
function configureActivation(baseUrl) {
|
|
12
|
+
_activationBaseUrl = baseUrl;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function register({ email, phoneNumber, name, password }) {
|
|
16
|
+
const normalized = normalizeEmail(email);
|
|
17
|
+
|
|
18
|
+
const existing = await User.query().findOne({ normalizedEmail: normalized });
|
|
19
|
+
if (existing) {
|
|
20
|
+
throw new Error('Email already registered');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const id = generateId();
|
|
24
|
+
const { hash, salt } = await hashPassword(password);
|
|
25
|
+
const now = formatDbDateTime();
|
|
26
|
+
const activationToken = crypto.randomBytes(32).toString('hex');
|
|
27
|
+
|
|
28
|
+
const user = await User.query().insert({
|
|
29
|
+
id,
|
|
30
|
+
email: email.trim().toLowerCase(),
|
|
31
|
+
normalizedEmail: normalized,
|
|
32
|
+
phoneNumber: phoneNumber || null,
|
|
33
|
+
name: name || null,
|
|
34
|
+
pwd: hash,
|
|
35
|
+
pwdSalt: salt,
|
|
36
|
+
isActive: true,
|
|
37
|
+
isActivated: false,
|
|
38
|
+
activationToken,
|
|
39
|
+
activatedOn: null,
|
|
40
|
+
activatedBy: null,
|
|
41
|
+
recordCreatedDate: now,
|
|
42
|
+
recordModifiedDate: now,
|
|
43
|
+
recordCreatedBy: id,
|
|
44
|
+
recordModifiedBy: id
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Send activation email
|
|
48
|
+
const baseUrl = _activationBaseUrl || process.env.ACTIVATION_BASE_URL;
|
|
49
|
+
if (!baseUrl) {
|
|
50
|
+
throw new Error('ACTIVATION_BASE_URL is required. Cannot register without email activation.');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const activationLink = baseUrl + '/auth/activate?token=' + activationToken;
|
|
54
|
+
const html = '<h2>Activate your account</h2>'
|
|
55
|
+
+ '<p>Hi ' + (name || 'there') + ',</p>'
|
|
56
|
+
+ '<p>Click below to activate your account:</p>'
|
|
57
|
+
+ '<p><a href="' + activationLink + '">' + activationLink + '</a></p>';
|
|
58
|
+
|
|
59
|
+
await sendEmail(email, 'Activate your account', html);
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
id: user.id,
|
|
63
|
+
email: user.email,
|
|
64
|
+
name: user.name,
|
|
65
|
+
phoneNumber: user.phoneNumber,
|
|
66
|
+
isActive: user.isActive,
|
|
67
|
+
isActivated: user.isActivated
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function activate(token) {
|
|
72
|
+
const user = await User.query().findOne({ activationToken: token });
|
|
73
|
+
if (!user) {
|
|
74
|
+
throw new Error('Invalid activation token');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (user.isActivated) {
|
|
78
|
+
throw new Error('Account already activated');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const now = formatDbDateTime();
|
|
82
|
+
await User.query().findById(user.id).patch({
|
|
83
|
+
isActivated: true,
|
|
84
|
+
activatedOn: now,
|
|
85
|
+
activationToken: null,
|
|
86
|
+
recordModifiedDate: now
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
return { id: user.id, email: user.email, name: user.name };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function login({ email, password }) {
|
|
93
|
+
const normalized = normalizeEmail(email);
|
|
94
|
+
const user = await User.query().findOne({ normalizedEmail: normalized });
|
|
95
|
+
if (!user) {
|
|
96
|
+
throw new Error('Invalid email or password');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!user.isActive) {
|
|
100
|
+
throw new Error('Account is deactivated');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const isMatch = await comparePassword(password, user.pwd);
|
|
104
|
+
if (!isMatch) {
|
|
105
|
+
throw new Error('Invalid email or password');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (!user.isActivated) {
|
|
109
|
+
throw new Error('NOT_ACTIVATED');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const { accessToken, refreshToken } = await createSession(user);
|
|
113
|
+
|
|
114
|
+
await clearUserAccess(user.id);
|
|
115
|
+
const access = await getUserAccess(user.id);
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
accessToken,
|
|
119
|
+
refreshToken,
|
|
120
|
+
user: {
|
|
121
|
+
id: user.id,
|
|
122
|
+
email: user.email,
|
|
123
|
+
name: user.name,
|
|
124
|
+
isActivated: user.isActivated
|
|
125
|
+
},
|
|
126
|
+
access
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function forgotPassword({ email }, resetBaseUrl) {
|
|
131
|
+
const normalized = normalizeEmail(email);
|
|
132
|
+
const user = await User.query().findOne({ normalizedEmail: normalized });
|
|
133
|
+
if (!user) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const resetToken = crypto.randomBytes(32).toString('hex');
|
|
138
|
+
const expiryMinutes = parseInt(process.env.RESET_TOKEN_EXPIRY_MINUTES) || 30;
|
|
139
|
+
const resetTokenExpiry = formatDbDateTime(new Date(Date.now() + expiryMinutes * 60 * 1000));
|
|
140
|
+
|
|
141
|
+
await User.query().findById(user.id).patch({
|
|
142
|
+
resetToken,
|
|
143
|
+
resetTokenExpiry,
|
|
144
|
+
recordModifiedDate: formatDbDateTime()
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const resetLink = `${resetBaseUrl}/auth/reset-password?token=${resetToken}`;
|
|
148
|
+
|
|
149
|
+
const html = `
|
|
150
|
+
<h2>Password Reset</h2>
|
|
151
|
+
<p>Hi ${user.name || 'there'},</p>
|
|
152
|
+
<p>You requested a password reset. Click the link below to reset your password:</p>
|
|
153
|
+
<p><a href="${resetLink}">${resetLink}</a></p>
|
|
154
|
+
<p>This link expires in ${expiryMinutes} minutes.</p>
|
|
155
|
+
<p>If you didn't request this, ignore this email.</p>
|
|
156
|
+
`;
|
|
157
|
+
|
|
158
|
+
await sendEmail(user.email, 'Password Reset', html);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function resetPassword({ token, newPassword }) {
|
|
162
|
+
const user = await User.query().findOne({ resetToken: token });
|
|
163
|
+
|
|
164
|
+
if (!user) {
|
|
165
|
+
throw new Error('Invalid or expired reset token');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (user.resetTokenExpiry < formatDbDateTime()) {
|
|
169
|
+
throw new Error('Invalid or expired reset token');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const { hash, salt } = await hashPassword(newPassword);
|
|
173
|
+
|
|
174
|
+
await User.query().findById(user.id).patch({
|
|
175
|
+
pwd: hash,
|
|
176
|
+
pwdSalt: salt,
|
|
177
|
+
resetToken: null,
|
|
178
|
+
resetTokenExpiry: null,
|
|
179
|
+
recordModifiedDate: formatDbDateTime()
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
await destroyAllUserSessions(user.id);
|
|
183
|
+
await clearUserAccess(user.id);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
module.exports = {
|
|
187
|
+
configureActivation,
|
|
188
|
+
configureEmail,
|
|
189
|
+
register,
|
|
190
|
+
activate,
|
|
191
|
+
login,
|
|
192
|
+
forgotPassword,
|
|
193
|
+
resetPassword
|
|
194
|
+
};
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session service — manages access + refresh token pairs in Redis.
|
|
3
|
+
*
|
|
4
|
+
* A "session" = one device/browser login, containing:
|
|
5
|
+
* - An access token (short-lived, 15m)
|
|
6
|
+
* - A refresh token (long-lived, 7d)
|
|
7
|
+
*
|
|
8
|
+
* Redis keys:
|
|
9
|
+
* session:{refreshToken} → { userId, accessToken, createdAt }
|
|
10
|
+
* access:{accessToken} → { userId, refreshToken } (for whitelist check)
|
|
11
|
+
* sessions:user:{userId} → Sorted Set of refreshTokens (score = createdAt)
|
|
12
|
+
*
|
|
13
|
+
* Env vars:
|
|
14
|
+
* REFRESH_TOKEN_TTL_DAYS — refresh token lifetime (default: 7)
|
|
15
|
+
* ACCESS_TOKEN_TTL_MINUTES — access token lifetime (default: 15)
|
|
16
|
+
* MAX_SESSIONS_PER_USER — max concurrent sessions (default: 5)
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const { cache } = require('@xeplr/utils');
|
|
20
|
+
const { generateRefreshToken, generateAccessToken } = require('./authHelper');
|
|
21
|
+
|
|
22
|
+
const SESSION_PREFIX = 'session:';
|
|
23
|
+
const ACCESS_PREFIX = 'access:';
|
|
24
|
+
const USER_PREFIX = 'sessions:user:';
|
|
25
|
+
|
|
26
|
+
function getRefreshTTL() {
|
|
27
|
+
const days = parseInt(process.env.REFRESH_TOKEN_TTL_DAYS || '7');
|
|
28
|
+
return days * 24 * 60 * 60;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function getAccessTTL() {
|
|
32
|
+
const minutes = parseInt(process.env.ACCESS_TOKEN_TTL_MINUTES || '15');
|
|
33
|
+
return minutes * 60;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function getMaxSessions() {
|
|
37
|
+
return parseInt(process.env.MAX_SESSIONS_PER_USER || '5');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Create a new session (access + refresh token pair).
|
|
42
|
+
* Evicts oldest session(s) if max sessions exceeded.
|
|
43
|
+
*
|
|
44
|
+
* @param {object} user - User object (id, email, name)
|
|
45
|
+
* @returns {{ accessToken, refreshToken }}
|
|
46
|
+
*/
|
|
47
|
+
async function createSession(user) {
|
|
48
|
+
const accessToken = generateAccessToken(user);
|
|
49
|
+
const refreshToken = generateRefreshToken();
|
|
50
|
+
const now = Date.now();
|
|
51
|
+
const refreshTTL = getRefreshTTL();
|
|
52
|
+
const accessTTL = getAccessTTL();
|
|
53
|
+
|
|
54
|
+
// Store session keyed by refresh token
|
|
55
|
+
await cache.set(SESSION_PREFIX + refreshToken, {
|
|
56
|
+
userId: user.id,
|
|
57
|
+
accessToken,
|
|
58
|
+
createdAt: now
|
|
59
|
+
}, refreshTTL);
|
|
60
|
+
|
|
61
|
+
// Store access token whitelist entry (for middleware check)
|
|
62
|
+
await cache.set(ACCESS_PREFIX + accessToken, {
|
|
63
|
+
userId: user.id,
|
|
64
|
+
refreshToken
|
|
65
|
+
}, accessTTL);
|
|
66
|
+
|
|
67
|
+
// Track session in user's sorted set
|
|
68
|
+
const client = cache.getClient();
|
|
69
|
+
try {
|
|
70
|
+
await client.zadd(USER_PREFIX + user.id, now, refreshToken);
|
|
71
|
+
await client.expire(USER_PREFIX + user.id, refreshTTL);
|
|
72
|
+
|
|
73
|
+
// Enforce session limit
|
|
74
|
+
await evictExcessSessions(user.id);
|
|
75
|
+
} catch (e) {
|
|
76
|
+
// Silently fail
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return { accessToken, refreshToken };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Evict oldest sessions if user exceeds MAX_SESSIONS_PER_USER.
|
|
84
|
+
*/
|
|
85
|
+
async function evictExcessSessions(userId) {
|
|
86
|
+
const client = cache.getClient();
|
|
87
|
+
const maxSessions = getMaxSessions();
|
|
88
|
+
const count = await client.zcard(USER_PREFIX + userId);
|
|
89
|
+
|
|
90
|
+
if (count <= maxSessions) return;
|
|
91
|
+
|
|
92
|
+
const excess = count - maxSessions;
|
|
93
|
+
const oldRefreshTokens = await client.zrange(USER_PREFIX + userId, 0, excess - 1);
|
|
94
|
+
|
|
95
|
+
if (oldRefreshTokens && oldRefreshTokens.length > 0) {
|
|
96
|
+
const pipeline = client.pipeline();
|
|
97
|
+
|
|
98
|
+
for (const rt of oldRefreshTokens) {
|
|
99
|
+
// Look up the session to find the paired access token
|
|
100
|
+
const sessionData = await cache.get(SESSION_PREFIX + rt);
|
|
101
|
+
if (sessionData && sessionData.accessToken) {
|
|
102
|
+
pipeline.del(ACCESS_PREFIX + sessionData.accessToken);
|
|
103
|
+
}
|
|
104
|
+
pipeline.del(SESSION_PREFIX + rt);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Remove from sorted set
|
|
108
|
+
pipeline.zremrangebyrank(USER_PREFIX + userId, 0, excess - 1);
|
|
109
|
+
await pipeline.exec();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Get session data by refresh token.
|
|
115
|
+
* Returns { userId, accessToken, createdAt } or null.
|
|
116
|
+
*/
|
|
117
|
+
async function getSession(refreshToken) {
|
|
118
|
+
return await cache.get(SESSION_PREFIX + refreshToken);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Validate an access token against the Redis whitelist.
|
|
123
|
+
* Returns { userId, refreshToken } if valid, null if revoked/expired.
|
|
124
|
+
*/
|
|
125
|
+
async function validateAccessToken(accessToken) {
|
|
126
|
+
return await cache.get(ACCESS_PREFIX + accessToken);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Rotate a session — validate old refresh token, create new pair.
|
|
131
|
+
* Returns { accessToken, refreshToken, userId } or null.
|
|
132
|
+
*/
|
|
133
|
+
async function rotateSession(oldRefreshToken, user) {
|
|
134
|
+
const session = await cache.get(SESSION_PREFIX + oldRefreshToken);
|
|
135
|
+
if (!session || !session.userId) return null;
|
|
136
|
+
|
|
137
|
+
// Delete old session
|
|
138
|
+
await destroySession(oldRefreshToken);
|
|
139
|
+
|
|
140
|
+
// Create new session (limit check happens inside)
|
|
141
|
+
const tokens = await createSession(user);
|
|
142
|
+
return { ...tokens, userId: session.userId };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Destroy a specific session (logout from one device).
|
|
147
|
+
*/
|
|
148
|
+
async function destroySession(refreshToken) {
|
|
149
|
+
const session = await cache.get(SESSION_PREFIX + refreshToken);
|
|
150
|
+
|
|
151
|
+
if (session) {
|
|
152
|
+
// Delete the access token whitelist entry
|
|
153
|
+
if (session.accessToken) {
|
|
154
|
+
await cache.del(ACCESS_PREFIX + session.accessToken);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Remove from user's sorted set
|
|
158
|
+
if (session.userId) {
|
|
159
|
+
const client = cache.getClient();
|
|
160
|
+
try {
|
|
161
|
+
await client.zrem(USER_PREFIX + session.userId, refreshToken);
|
|
162
|
+
} catch (e) {
|
|
163
|
+
// Silently fail
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Delete the session itself
|
|
169
|
+
await cache.del(SESSION_PREFIX + refreshToken);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Destroy ALL sessions for a user (password change, force logout all devices).
|
|
174
|
+
*/
|
|
175
|
+
async function destroyAllUserSessions(userId) {
|
|
176
|
+
const client = cache.getClient();
|
|
177
|
+
try {
|
|
178
|
+
const refreshTokens = await client.zrange(USER_PREFIX + userId, 0, -1);
|
|
179
|
+
|
|
180
|
+
if (refreshTokens && refreshTokens.length > 0) {
|
|
181
|
+
const pipeline = client.pipeline();
|
|
182
|
+
|
|
183
|
+
for (const rt of refreshTokens) {
|
|
184
|
+
const session = await cache.get(SESSION_PREFIX + rt);
|
|
185
|
+
if (session && session.accessToken) {
|
|
186
|
+
pipeline.del(ACCESS_PREFIX + session.accessToken);
|
|
187
|
+
}
|
|
188
|
+
pipeline.del(SESSION_PREFIX + rt);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
pipeline.del(USER_PREFIX + userId);
|
|
192
|
+
await pipeline.exec();
|
|
193
|
+
} else {
|
|
194
|
+
await client.del(USER_PREFIX + userId);
|
|
195
|
+
}
|
|
196
|
+
} catch (e) {
|
|
197
|
+
// Fallback
|
|
198
|
+
await cache.delPattern('session:*');
|
|
199
|
+
await cache.delPattern('access:*');
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
module.exports = {
|
|
204
|
+
createSession,
|
|
205
|
+
getSession,
|
|
206
|
+
validateAccessToken,
|
|
207
|
+
rotateSession,
|
|
208
|
+
destroySession,
|
|
209
|
+
destroyAllUserSessions
|
|
210
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
exports.up = function(knex) {
|
|
2
|
+
return knex.schema.createTable('users', function(table) {
|
|
3
|
+
table.string('id', 25).primary();
|
|
4
|
+
table.string('email', 255).notNullable();
|
|
5
|
+
table.string('phoneNumber', 255);
|
|
6
|
+
table.string('name', 255);
|
|
7
|
+
table.string('pwd', 255);
|
|
8
|
+
table.string('pwdSalt', 100);
|
|
9
|
+
table.boolean('isActive').defaultTo(false);
|
|
10
|
+
table.boolean('isActivated').defaultTo(false);
|
|
11
|
+
table.timestamp('activatedOn');
|
|
12
|
+
table.string('activatedBy', 25);
|
|
13
|
+
table.timestamp('recordCreatedDate');
|
|
14
|
+
table.timestamp('recordModifiedDate');
|
|
15
|
+
table.string('recordCreatedBy', 25);
|
|
16
|
+
table.string('recordModifiedBy', 25);
|
|
17
|
+
});
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
exports.down = function(knex) {
|
|
21
|
+
return knex.schema.dropTableIfExists('users');
|
|
22
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
exports.up = function(knex) {
|
|
2
|
+
return knex.schema.createTable('menus', function(table) {
|
|
3
|
+
table.string('id', 25).primary();
|
|
4
|
+
table.string('name', 255);
|
|
5
|
+
table.string('menuGroup', 255);
|
|
6
|
+
table.boolean('isActive').defaultTo(false);
|
|
7
|
+
table.timestamp('recordCreatedDate');
|
|
8
|
+
table.timestamp('recordModifiedDate');
|
|
9
|
+
table.string('recordCreatedBy', 25);
|
|
10
|
+
table.string('recordModifiedBy', 25);
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
exports.down = function(knex) {
|
|
15
|
+
return knex.schema.dropTableIfExists('menus');
|
|
16
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
exports.up = function(knex) {
|
|
2
|
+
return knex.schema.createTable('apis', function(table) {
|
|
3
|
+
table.string('id', 25).primary();
|
|
4
|
+
table.string('name', 255);
|
|
5
|
+
table.string('apiGroup', 255);
|
|
6
|
+
table.boolean('isActive').defaultTo(false);
|
|
7
|
+
table.timestamp('recordCreatedDate');
|
|
8
|
+
table.timestamp('recordModifiedDate');
|
|
9
|
+
table.string('recordCreatedBy', 25);
|
|
10
|
+
table.string('recordModifiedBy', 25);
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
exports.down = function(knex) {
|
|
15
|
+
return knex.schema.dropTableIfExists('apis');
|
|
16
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
exports.up = function(knex) {
|
|
2
|
+
return knex.schema.createTable('uiPages', function(table) {
|
|
3
|
+
table.string('id', 25).primary();
|
|
4
|
+
table.string('name', 255);
|
|
5
|
+
table.string('uiPagesGroup', 255);
|
|
6
|
+
table.boolean('isActive').defaultTo(false);
|
|
7
|
+
table.timestamp('recordCreatedDate');
|
|
8
|
+
table.timestamp('recordModifiedDate');
|
|
9
|
+
table.string('recordCreatedBy', 25);
|
|
10
|
+
table.string('recordModifiedBy', 25);
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
exports.down = function(knex) {
|
|
15
|
+
return knex.schema.dropTableIfExists('uiPages');
|
|
16
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
exports.up = function(knex) {
|
|
2
|
+
return knex.schema.createTable('uiElements', function(table) {
|
|
3
|
+
table.string('id', 25).primary();
|
|
4
|
+
table.string('name', 255);
|
|
5
|
+
table.string('uiElementsGroup', 255);
|
|
6
|
+
table.boolean('isActive').defaultTo(false);
|
|
7
|
+
table.timestamp('recordCreatedDate');
|
|
8
|
+
table.timestamp('recordModifiedDate');
|
|
9
|
+
table.string('recordCreatedBy', 25);
|
|
10
|
+
table.string('recordModifiedBy', 25);
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
exports.down = function(knex) {
|
|
15
|
+
return knex.schema.dropTableIfExists('uiElements');
|
|
16
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
exports.up = function(knex) {
|
|
2
|
+
return knex.schema.createTable('roles', function(table) {
|
|
3
|
+
table.string('id', 25).primary();
|
|
4
|
+
table.string('name', 255);
|
|
5
|
+
table.boolean('isActive').defaultTo(false);
|
|
6
|
+
table.timestamp('recordCreatedDate');
|
|
7
|
+
table.timestamp('recordModifiedDate');
|
|
8
|
+
table.string('recordCreatedBy', 25);
|
|
9
|
+
table.string('recordModifiedBy', 25);
|
|
10
|
+
});
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
exports.down = function(knex) {
|
|
14
|
+
return knex.schema.dropTableIfExists('roles');
|
|
15
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
exports.up = function(knex) {
|
|
2
|
+
return knex.schema.createTable('apisRolesMapping', function(table) {
|
|
3
|
+
table.string('id', 25).primary();
|
|
4
|
+
table.string('roleId', 25).references('id').inTable('roles');
|
|
5
|
+
table.string('apiId', 25).references('id').inTable('apis');
|
|
6
|
+
table.boolean('isActive').defaultTo(false);
|
|
7
|
+
table.timestamp('recordCreatedDate');
|
|
8
|
+
table.timestamp('recordModifiedDate');
|
|
9
|
+
table.string('recordCreatedBy', 25);
|
|
10
|
+
table.string('recordModifiedBy', 25);
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
exports.down = function(knex) {
|
|
15
|
+
return knex.schema.dropTableIfExists('apisRolesMapping');
|
|
16
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
exports.up = function(knex) {
|
|
2
|
+
return knex.schema.createTable('uiPagesRolesMapping', function(table) {
|
|
3
|
+
table.string('id', 25).primary();
|
|
4
|
+
table.string('roleId', 25).references('id').inTable('roles');
|
|
5
|
+
table.string('uiPageId', 25).references('id').inTable('uiPages');
|
|
6
|
+
table.boolean('isActive').defaultTo(false);
|
|
7
|
+
table.timestamp('recordCreatedDate');
|
|
8
|
+
table.timestamp('recordModifiedDate');
|
|
9
|
+
table.string('recordCreatedBy', 25);
|
|
10
|
+
table.string('recordModifiedBy', 25);
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
exports.down = function(knex) {
|
|
15
|
+
return knex.schema.dropTableIfExists('uiPagesRolesMapping');
|
|
16
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
exports.up = function(knex) {
|
|
2
|
+
return knex.schema.createTable('uiElementsRolesMapping', function(table) {
|
|
3
|
+
table.string('id', 25).primary();
|
|
4
|
+
table.string('roleId', 25).references('id').inTable('roles');
|
|
5
|
+
table.string('uiElementId', 25).references('id').inTable('uiElements');
|
|
6
|
+
table.boolean('isActive').defaultTo(false);
|
|
7
|
+
table.timestamp('recordCreatedDate');
|
|
8
|
+
table.timestamp('recordModifiedDate');
|
|
9
|
+
table.string('recordCreatedBy', 25);
|
|
10
|
+
table.string('recordModifiedBy', 25);
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
exports.down = function(knex) {
|
|
15
|
+
return knex.schema.dropTableIfExists('uiElementsRolesMapping');
|
|
16
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
exports.up = function(knex) {
|
|
2
|
+
return knex.schema.createTable('menuRolesMapping', function(table) {
|
|
3
|
+
table.string('id', 25).primary();
|
|
4
|
+
table.string('menuId', 25).references('id').inTable('menus');
|
|
5
|
+
table.string('roleId', 25).references('id').inTable('roles');
|
|
6
|
+
table.boolean('isActive').defaultTo(false);
|
|
7
|
+
table.timestamp('recordCreatedDate');
|
|
8
|
+
table.timestamp('recordModifiedDate');
|
|
9
|
+
table.string('recordCreatedBy', 25);
|
|
10
|
+
table.string('recordModifiedBy', 25);
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
exports.down = function(knex) {
|
|
15
|
+
return knex.schema.dropTableIfExists('menuRolesMapping');
|
|
16
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
exports.up = function(knex) {
|
|
2
|
+
return knex.schema.createTable('userRolesMapping', function(table) {
|
|
3
|
+
table.string('id', 25).primary();
|
|
4
|
+
table.string('userId', 25).references('id').inTable('users');
|
|
5
|
+
table.string('roleId', 25).references('id').inTable('roles');
|
|
6
|
+
table.boolean('isActive').defaultTo(false);
|
|
7
|
+
table.timestamp('recordCreatedDate');
|
|
8
|
+
table.timestamp('recordModifiedDate');
|
|
9
|
+
table.string('recordCreatedBy', 25);
|
|
10
|
+
table.string('recordModifiedBy', 25);
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
exports.down = function(knex) {
|
|
15
|
+
return knex.schema.dropTableIfExists('userRolesMapping');
|
|
16
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
exports.up = function(knex) {
|
|
2
|
+
return knex.schema.alterTable('users', function(table) {
|
|
3
|
+
table.string('resetToken', 255);
|
|
4
|
+
table.timestamp('resetTokenExpiry');
|
|
5
|
+
});
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
exports.down = function(knex) {
|
|
9
|
+
return knex.schema.alterTable('users', function(table) {
|
|
10
|
+
table.dropColumn('resetToken');
|
|
11
|
+
table.dropColumn('resetTokenExpiry');
|
|
12
|
+
});
|
|
13
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
exports.up = function(knex) {
|
|
2
|
+
return Promise.all([
|
|
3
|
+
knex.schema.alterTable('apis', function(table) {
|
|
4
|
+
table.boolean('isPublic').defaultTo(false);
|
|
5
|
+
}),
|
|
6
|
+
knex.schema.alterTable('uiPages', function(table) {
|
|
7
|
+
table.boolean('isPublic').defaultTo(false);
|
|
8
|
+
}),
|
|
9
|
+
knex.schema.alterTable('menus', function(table) {
|
|
10
|
+
table.boolean('isPublic').defaultTo(false);
|
|
11
|
+
})
|
|
12
|
+
]);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
exports.down = function(knex) {
|
|
16
|
+
return Promise.all([
|
|
17
|
+
knex.schema.alterTable('apis', function(table) {
|
|
18
|
+
table.dropColumn('isPublic');
|
|
19
|
+
}),
|
|
20
|
+
knex.schema.alterTable('uiPages', function(table) {
|
|
21
|
+
table.dropColumn('isPublic');
|
|
22
|
+
}),
|
|
23
|
+
knex.schema.alterTable('menus', function(table) {
|
|
24
|
+
table.dropColumn('isPublic');
|
|
25
|
+
})
|
|
26
|
+
]);
|
|
27
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
exports.up = function(knex) {
|
|
2
|
+
return knex.schema.alterTable('users', function(table) {
|
|
3
|
+
table.string('activationToken', 255);
|
|
4
|
+
table.string('normalizedEmail', 255).index();
|
|
5
|
+
});
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
exports.down = function(knex) {
|
|
9
|
+
return knex.schema.alterTable('users', function(table) {
|
|
10
|
+
table.dropColumn('activationToken');
|
|
11
|
+
table.dropColumn('normalizedEmail');
|
|
12
|
+
});
|
|
13
|
+
};
|