Merge branch 'obtain_access_token_feature' into refresh_access_token
This commit is contained in:
@@ -12,3 +12,10 @@ RATE_LIMIT_MAX_REQUESTS=10
|
|||||||
DB=foobar.db
|
DB=foobar.db
|
||||||
|
|
||||||
START_WITH_STUDIO=false
|
START_WITH_STUDIO=false
|
||||||
|
|
||||||
|
TOKEN_SECRET=ABCD23DCAA
|
||||||
|
ACCESS_TOKEN_EXPIRATION_TIME=10H
|
||||||
|
REFRESH_TOKEN_EXPIRATION_TIME=2D
|
||||||
|
|
||||||
|
INITIAL_USER_USERNAME=superuser
|
||||||
|
INITIAL_USER_PASSWORD=hello@3245CD$
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ To run Soul in auth mode, allowing login and signup features with authorization
|
|||||||
Run the Soul command with the necessary parameters:
|
Run the Soul command with the necessary parameters:
|
||||||
|
|
||||||
```
|
```
|
||||||
soul --d foobar.db -a -ats <your_jwt_access_token_secret_value> -atet=4H -rts <your_jwt_refresh_token_secret_value> -rtet=3D
|
soul --d foobar.db -a -ts <your_jwt_secret_value> -atet=4H -rtet=3D
|
||||||
```
|
```
|
||||||
|
|
||||||
Note: When configuring your JWT Secret, it is recommended to use a long string value for enhanced security. It is advisable to use a secret that is at least 10 characters in length.
|
Note: When configuring your JWT Secret, it is recommended to use a long string value for enhanced security. It is advisable to use a secret that is at least 10 characters in length.
|
||||||
@@ -67,9 +67,8 @@ Note: When configuring your JWT Secret, it is recommended to use a long string v
|
|||||||
In this example:
|
In this example:
|
||||||
|
|
||||||
The `-a` flag instructs Soul to run in auth mode.
|
The `-a` flag instructs Soul to run in auth mode.
|
||||||
The `-ats` flag allows you to pass a JWT secret value for the `access token` generation and verification. Replace <your_jwt_access_token_secret_value> with your desired secret value.
|
The `-ts` flag allows you to pass a JWT secret value for the `access and refresh tokens` generation and verification. Replace <your_jwt\_\_secret_value> with your desired secret value.
|
||||||
The `-atet` flag sets the JWT expiration time for the access token. In this case, it is set to four hours (4H), meaning the token will expire after 4 hours.
|
The `-atet` flag sets the JWT expiration time for the access token. In this case, it is set to four hours (4H), meaning the token will expire after 4 hours.
|
||||||
The `-rts` flag allows you to pass a JWT secret value for the `refresh token` generation and verification. Replace <your_jwt_refresh_token_secret_value> with your desired secret value.
|
|
||||||
Teh `-rtet` flag sets the JWT expiration time for the refresh token. In this case, it is set to three days (3D), meaning the token will expire after 3 days.
|
Teh `-rtet` flag sets the JWT expiration time for the refresh token. In this case, it is set to three days (3D), meaning the token will expire after 3 days.
|
||||||
|
|
||||||
Here are some example values for the `-atet` and `rtet` flags
|
Here are some example values for the `-atet` and `rtet` flags
|
||||||
@@ -78,7 +77,7 @@ Here are some example values for the `-atet` and `rtet` flags
|
|||||||
- 5H: Represents a duration of 5 hours.
|
- 5H: Represents a duration of 5 hours.
|
||||||
- 1D: Represents a duration of 1 day.
|
- 1D: Represents a duration of 1 day.
|
||||||
|
|
||||||
NOTE: It is crucial to securely store a copy of the `Access token secret` and `Refresh token secret` values used in Soul. Once you pass this values, make sure to keep a backup because you will need it every time you restart Soul. Losing this secret values can result in a situation where all of your users are blocked from accessing Soul.
|
NOTE: It is crucial to securely store a copy of the `-ts`(`Token Secret`) value used in Soul. Once you pass this values, make sure to keep a backup because you will need it every time you restart Soul. Losing this secret values can result in a situation where all of your users are blocked from accessing Soul.
|
||||||
|
|
||||||
### 3. Updating Super Users
|
### 3. Updating Super Users
|
||||||
|
|
||||||
|
|||||||
13
src/cli.js
13
src/cli.js
@@ -53,9 +53,9 @@ if (process.env.NO_CLI !== 'true') {
|
|||||||
default: false,
|
default: false,
|
||||||
demandOption: false,
|
demandOption: false,
|
||||||
})
|
})
|
||||||
.options('ats', {
|
.options('ts', {
|
||||||
alias: 'accesstokensecret',
|
alias: 'tokensecret',
|
||||||
describe: 'JWT secret for access token',
|
describe: 'JWT secret for the access and refresh tokens',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
default: null,
|
default: null,
|
||||||
demandOption: false,
|
demandOption: false,
|
||||||
@@ -67,13 +67,6 @@ if (process.env.NO_CLI !== 'true') {
|
|||||||
default: '5H',
|
default: '5H',
|
||||||
demandOption: false,
|
demandOption: false,
|
||||||
})
|
})
|
||||||
.options('rts', {
|
|
||||||
alias: 'refreshtokensecret',
|
|
||||||
describe: 'JWT secret for refresh token',
|
|
||||||
type: 'string',
|
|
||||||
default: null,
|
|
||||||
demandOption: false,
|
|
||||||
})
|
|
||||||
.options('rtet', {
|
.options('rtet', {
|
||||||
alias: 'refreshtokenexpirationtime',
|
alias: 'refreshtokenexpirationtime',
|
||||||
describe: 'JWT expiration time for refresh token',
|
describe: 'JWT expiration time for refresh token',
|
||||||
|
|||||||
@@ -30,9 +30,8 @@ const envVarsSchema = Joi.object()
|
|||||||
|
|
||||||
START_WITH_STUDIO: Joi.boolean().default(false),
|
START_WITH_STUDIO: Joi.boolean().default(false),
|
||||||
|
|
||||||
ACCESS_TOKEN_SECRET: Joi.string().default(null),
|
TOKEN_SECRET: Joi.string().default(null),
|
||||||
ACCESS_TOKEN_EXPIRATION_TIME: Joi.string().default('5H'),
|
ACCESS_TOKEN_EXPIRATION_TIME: Joi.string().default('5H'),
|
||||||
REFRESH_TOKEN_SECRET: Joi.string().default(null),
|
|
||||||
REFRESH_TOKEN_EXPIRATION_TIME: Joi.string().default('3D'),
|
REFRESH_TOKEN_EXPIRATION_TIME: Joi.string().default('3D'),
|
||||||
})
|
})
|
||||||
.unknown();
|
.unknown();
|
||||||
@@ -65,18 +64,14 @@ if (argv['rate-limit-enabled']) {
|
|||||||
env.RATE_LIMIT_ENABLED = argv['rate-limit-enabled'];
|
env.RATE_LIMIT_ENABLED = argv['rate-limit-enabled'];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argv.accesstokensecret) {
|
if (argv.tokensecret) {
|
||||||
env.ACCESS_TOKEN_SECRET = argv.accesstokensecret;
|
env.TOKEN_SECRET = argv.tokensecret;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argv.accesstokenexpirationtime) {
|
if (argv.accesstokenexpirationtime) {
|
||||||
env.ACCESS_TOKEN_EXPIRATION_TIME = argv.accesstokenexpirationtime;
|
env.ACCESS_TOKEN_EXPIRATION_TIME = argv.accesstokenexpirationtime;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argv.refreshtokensecret) {
|
|
||||||
env.REFRESH_TOKEN_SECRET = argv.refreshtokensecret;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (argv.refreshtokenexpirationtime) {
|
if (argv.refreshtokenexpirationtime) {
|
||||||
env.REFRESH_TOKEN_EXPIRATION_TIME = argv.refreshtokenexpirationtime;
|
env.REFRESH_TOKEN_EXPIRATION_TIME = argv.refreshtokenexpirationtime;
|
||||||
}
|
}
|
||||||
@@ -108,10 +103,9 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
auth: argv.auth || envVars.AUTH,
|
auth: argv.auth || envVars.AUTH,
|
||||||
accessTokenSecret: argv.accesstokensecret || envVars.ACCESS_TOKEN_SECRET,
|
tokenSecret: argv.tokensecret || envVars.TOKEN_SECRET,
|
||||||
accessTokenExpirationTime:
|
accessTokenExpirationTime:
|
||||||
argv.accesstokenexpirationtime || envVars.ACCESS_TOKEN_EXPIRATION_TIME,
|
argv.accesstokenexpirationtime || envVars.ACCESS_TOKEN_EXPIRATION_TIME,
|
||||||
refreshTokenSecret: argv.refreshtokensecret || envVars.REFRESH_TOKEN_SECRET,
|
|
||||||
refreshTokenExpirationTime:
|
refreshTokenExpirationTime:
|
||||||
argv.refreshtokenexpirationtime || envVars.REFRESH_TOKEN_EXPIRATION_TIME,
|
argv.refreshtokenexpirationtime || envVars.REFRESH_TOKEN_EXPIRATION_TIME,
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
defaultRoutes: ['_users', '_roles', '_roles_permissions', '_users_roles'],
|
defaultRoutes: ['_users', '_roles', '_roles_permissions', '_users_roles'],
|
||||||
|
DEFAULT_PAGE_LIMIT: 10,
|
||||||
|
DEFAULT_PAGE_INDEX: 0,
|
||||||
|
PASSWORD: {
|
||||||
|
TOO_WEAK: 'Too weak',
|
||||||
|
WEAK: 'Weak',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
const { tableService } = require('../services');
|
const { tableService, rowService } = require('../services');
|
||||||
const { rowService } = require('../services');
|
|
||||||
const { dbTables, constantRoles } = require('../constants');
|
|
||||||
const config = require('../config');
|
const config = require('../config');
|
||||||
const {
|
const {
|
||||||
hashPassword,
|
hashPassword,
|
||||||
@@ -10,6 +8,8 @@ const {
|
|||||||
decodeToken,
|
decodeToken,
|
||||||
} = require('../utils');
|
} = require('../utils');
|
||||||
|
|
||||||
|
const { dbTables, constantRoles, apiConstants } = require('../constants');
|
||||||
|
|
||||||
const createDefaultTables = async () => {
|
const createDefaultTables = async () => {
|
||||||
let roleId;
|
let roleId;
|
||||||
|
|
||||||
@@ -90,28 +90,31 @@ const updateSuperuser = async (fields) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// find the user by using the id field
|
// find the user by using the id field
|
||||||
let user = rowService.get({
|
const users = rowService.get({
|
||||||
tableName: '_users',
|
tableName: '_users',
|
||||||
whereString: 'WHERE id=?',
|
whereString: 'WHERE id=?',
|
||||||
whereStringValues: [id],
|
whereStringValues: [id],
|
||||||
});
|
});
|
||||||
|
|
||||||
// abort if the id is invalid
|
// abort if the id is invalid
|
||||||
if (user.length === 0) {
|
if (users.length === 0) {
|
||||||
console.log('The user id you passed does not exist in the database');
|
console.log('The user id you passed does not exist in the database');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
user = user[0];
|
|
||||||
|
|
||||||
// check if the is_superuser field is passed
|
// check if the is_superuser field is passed
|
||||||
if (is_superuser !== undefined) {
|
if (is_superuser !== undefined) {
|
||||||
fieldsString = `is_superuser = '${is_superuser}', `;
|
fieldsString = `is_superuser = '${is_superuser}'`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the password is sent from the CLI, update it
|
// if the password is sent from the CLI, update it
|
||||||
if (password) {
|
if (password) {
|
||||||
if (password.length < 8) {
|
// check if the password is weak
|
||||||
|
if (
|
||||||
|
[apiConstants.PASSWORD.TOO_WEAK, apiConstants.PASSWORD.WEAK].includes(
|
||||||
|
checkPasswordStrength(password),
|
||||||
|
)
|
||||||
|
) {
|
||||||
console.log('Your password should be at least 8 charachters long');
|
console.log('Your password should be at least 8 charachters long');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
@@ -120,7 +123,10 @@ const updateSuperuser = async (fields) => {
|
|||||||
const { hashedPassword, salt } = await hashPassword(password, 10);
|
const { hashedPassword, salt } = await hashPassword(password, 10);
|
||||||
newHashedPassword = hashedPassword;
|
newHashedPassword = hashedPassword;
|
||||||
newSalt = salt;
|
newSalt = salt;
|
||||||
fieldsString += `hashed_password = '${newHashedPassword}', salt = '${newSalt}'`;
|
|
||||||
|
fieldsString = `${
|
||||||
|
fieldsString ? fieldsString + ', ' : ''
|
||||||
|
}hashed_password = '${newHashedPassword}', salt = '${newSalt}'`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// update the user
|
// update the user
|
||||||
@@ -156,7 +162,11 @@ const registerUser = async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// check if the password is weak
|
// check if the password is weak
|
||||||
if (['Too weak', 'Weak'].includes(checkPasswordStrength(password))) {
|
if (
|
||||||
|
[apiConstants.PASSWORD.TOO_WEAK, apiConstants.PASSWORD.WEAK].includes(
|
||||||
|
checkPasswordStrength(password),
|
||||||
|
)
|
||||||
|
) {
|
||||||
return res.status(400).send({
|
return res.status(400).send({
|
||||||
message: 'This password is weak, please use another password',
|
message: 'This password is weak, please use another password',
|
||||||
});
|
});
|
||||||
@@ -165,7 +175,7 @@ const registerUser = async (req, res) => {
|
|||||||
// hash the password
|
// hash the password
|
||||||
const { salt, hashedPassword } = await hashPassword(password, 10);
|
const { salt, hashedPassword } = await hashPassword(password, 10);
|
||||||
|
|
||||||
// // create the user
|
// create the user
|
||||||
const newUser = rowService.save({
|
const newUser = rowService.save({
|
||||||
tableName: '_users',
|
tableName: '_users',
|
||||||
fields: {
|
fields: {
|
||||||
@@ -180,7 +190,7 @@ const registerUser = async (req, res) => {
|
|||||||
let defaultRole = rowService.get({
|
let defaultRole = rowService.get({
|
||||||
tableName: '_roles',
|
tableName: '_roles',
|
||||||
whereString: 'WHERE name=?',
|
whereString: 'WHERE name=?',
|
||||||
whereStringValues: ['default'],
|
whereStringValues: [constantRoles.DEFAULT_ROLE],
|
||||||
});
|
});
|
||||||
|
|
||||||
if (defaultRole.length <= 0) {
|
if (defaultRole.length <= 0) {
|
||||||
@@ -246,14 +256,14 @@ const obtainAccessToken = async (req, res) => {
|
|||||||
// generate an access token
|
// generate an access token
|
||||||
const accessToken = await generateToken(
|
const accessToken = await generateToken(
|
||||||
{ subject: 'accessToken', ...payload },
|
{ subject: 'accessToken', ...payload },
|
||||||
config.accessTokenSecret,
|
config.tokenSecret,
|
||||||
config.accessTokenExpirationTime,
|
config.accessTokenExpirationTime,
|
||||||
);
|
);
|
||||||
|
|
||||||
// generate a refresh token
|
// generate a refresh token
|
||||||
const refreshToken = await generateToken(
|
const refreshToken = await generateToken(
|
||||||
{ subject: 'refreshToken', ...payload },
|
{ subject: 'refreshToken', ...payload },
|
||||||
config.refreshTokenSecret,
|
config.tokenSecret,
|
||||||
config.refreshTokenExpirationTime,
|
config.refreshTokenExpirationTime,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,6 @@
|
|||||||
const db = require('../db/index');
|
const db = require('../db/index');
|
||||||
const { rowService } = require('../services');
|
const { rowService } = require('../services');
|
||||||
|
|
||||||
// const quotePrimaryKeys = (pks) => {
|
|
||||||
// const primaryKeys = pks.split(',');
|
|
||||||
// const quotedPks = primaryKeys.map((id) => `'${id}'`).join(',');
|
|
||||||
// return quotedPks;
|
|
||||||
// };
|
|
||||||
|
|
||||||
const operators = {
|
const operators = {
|
||||||
eq: '=',
|
eq: '=',
|
||||||
lt: '<',
|
lt: '<',
|
||||||
@@ -327,13 +321,6 @@ const listTableRows = async (req, res, next) => {
|
|||||||
}`
|
}`
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
// res.json({
|
|
||||||
// data,
|
|
||||||
// total,
|
|
||||||
// next: nextPage,
|
|
||||||
// previous
|
|
||||||
// });
|
|
||||||
|
|
||||||
req.response = {
|
req.response = {
|
||||||
status: 200,
|
status: 200,
|
||||||
payload: { data, total, next: nextPage, previous },
|
payload: { data, total, next: nextPage, previous },
|
||||||
@@ -598,10 +585,6 @@ const getRowInTableByPK = async (req, res, next) => {
|
|||||||
error: 'not_found',
|
error: 'not_found',
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// res.json({
|
|
||||||
// data
|
|
||||||
// });
|
|
||||||
|
|
||||||
req.response = { status: 200, payload: { data } };
|
req.response = { status: 200, payload: { data } };
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ const processRequest = async (req, res, next) => {
|
|||||||
|
|
||||||
// If the user sends a request when auth is set to false, throw an error
|
// If the user sends a request when auth is set to false, throw an error
|
||||||
if (apiConstants.defaultRoutes.includes(resource) && !config.auth) {
|
if (apiConstants.defaultRoutes.includes(resource) && !config.auth) {
|
||||||
return res.status(401).send({
|
return res.status(403).send({
|
||||||
message: 'You can not access this endpoint while AUTH is set to false',
|
message: 'You can not access this endpoint while AUTH is set to false',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
const { apiConstants } = require('../constants');
|
||||||
|
|
||||||
module.exports = (db) => {
|
module.exports = (db) => {
|
||||||
return {
|
return {
|
||||||
get(data) {
|
get(data) {
|
||||||
@@ -10,8 +12,8 @@ module.exports = (db) => {
|
|||||||
const statement = db.prepare(query);
|
const statement = db.prepare(query);
|
||||||
const result = statement.all(
|
const result = statement.all(
|
||||||
...data.whereStringValues,
|
...data.whereStringValues,
|
||||||
data.limit || 10,
|
data.limit || apiConstants.DEFAULT_PAGE_LIMIT,
|
||||||
data.page || 0,
|
data.page || apiConstants.DEFAULT_PAGE_INDEX,
|
||||||
);
|
);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -242,8 +242,8 @@
|
|||||||
"400": {
|
"400": {
|
||||||
"description": "Bad Request"
|
"description": "Bad Request"
|
||||||
},
|
},
|
||||||
"401": {
|
"403": {
|
||||||
"description": "Unauthorized"
|
"description": "Forbidden"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -281,8 +281,8 @@
|
|||||||
"$ref": "#/definitions/InsertRowErrorResponse"
|
"$ref": "#/definitions/InsertRowErrorResponse"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"401": {
|
"403": {
|
||||||
"description": "Unauthorized"
|
"description": "Forbidden"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user