Merge branch 'obtain_access_token_feature' into refresh_access_token

This commit is contained in:
AbegaM
2024-03-08 17:05:45 +03:00
10 changed files with 57 additions and 63 deletions

View File

@@ -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$

View File

@@ -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

View File

@@ -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',

View File

@@ -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,

View File

@@ -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',
},
}; };

View File

@@ -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,
); );

View File

@@ -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();
} }

View File

@@ -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',
}); });
} }

View File

@@ -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;

View File

@@ -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"
} }
} }
} }