Add a feature to handle revoked refresh tokens

This commit is contained in:
AbegaM
2024-04-01 14:10:29 +03:00
parent 691d6f1cc7
commit 0bf3778d23
5 changed files with 86 additions and 4 deletions

View File

@@ -2,12 +2,15 @@ const USERS_TABLE = '_users';
const ROLES_TABLE = '_roles';
const USERS_ROLES_TABLE = '_users_roles';
const ROLES_PERMISSIONS_TABLE = '_roles_permissions';
const REVOKED_REFRESH_TOKENS_TABLE = '_revoked_refresh_tokens';
module.exports = {
// db table names
USERS_TABLE,
ROLES_TABLE,
USERS_ROLES_TABLE,
ROLES_PERMISSIONS_TABLE,
REVOKED_REFRESH_TOKENS_TABLE,
reservedTableNames: [
USERS_TABLE,
@@ -43,5 +46,9 @@ module.exports = {
// _users_roles fields
USER_ID: 'user_id',
//_revoked_refresh_tokens
REFRESH_TOKEN: 'refresh_token',
EXPIRES_AT: 'expires_at',
},
};

View File

@@ -7,6 +7,7 @@ const {
ROLES_TABLE,
USERS_ROLES_TABLE,
ROLES_PERMISSIONS_TABLE,
REVOKED_REFRESH_TOKENS_TABLE,
constraints,
tableFields,
} = dbConstants;
@@ -21,6 +22,9 @@ const createDefaultTables = async () => {
ROLES_PERMISSIONS_TABLE,
);
const usersRolesTable = tableService.checkTableExists(USERS_ROLES_TABLE);
const revokedRefreshTokensTable = tableService.checkTableExists(
REVOKED_REFRESH_TOKENS_TABLE,
);
// create _users table
if (!usersTable) {
@@ -89,6 +93,14 @@ const createDefaultTables = async () => {
fields: permissions,
});
}
// create _revoked_refresh_tokens table
if (!revokedRefreshTokensTable) {
tableService.createTable(
REVOKED_REFRESH_TOKENS_TABLE,
schema.revokedRefreshTokensSchema,
);
}
};
module.exports = {

View File

@@ -123,13 +123,18 @@ const refreshAccessToken = async (req, res) => {
#swagger.summary = 'Refresh Access Token'
#swagger.description = 'Endpoint to refresh access and refresh tokens'
*/
const refToken = req.cookies.refreshToken;
try {
// check if the refresh token is revoked
if (isRefreshTokenRevoked({ refreshToken: refToken })) {
return res
.status(403)
.send({ message: errorMessage.INVALID_REFRESH_TOKEN_ERROR });
}
// extract the payload from the token and verify it
const payload = await decodeToken(
req.cookies.refreshToken,
config.tokenSecret,
);
const payload = await decodeToken(refToken, config.tokenSecret);
// find the user
const users = authService.getUsersById({ userId: payload.userId });
@@ -224,7 +229,19 @@ const removeTokens = async (req, res) => {
#swagger.description = 'Endpoint to remove access and refresh tokens'
*/
const refreshToken = req.cookies.refreshToken;
try {
// decode the token
const payload = await decodeToken(refreshToken, config.tokenSecret);
// store the refresh token in the _revoked_refresh_tokens table
authService.saveRevokedRefreshToken({
refreshToken,
expiresAt: payload.exp,
});
// remove the token from the cookie
res.clearCookie(authConstants.ACCESS_TOKEN_SUBJECT);
res.clearCookie(authConstants.REFRESH_TOKEN_SUBJECT);
@@ -261,6 +278,11 @@ const getUsersRoleAndPermission = ({ userId, res }) => {
return { userRoles, roleIds, permissions };
};
const isRefreshTokenRevoked = ({ refreshToken }) => {
const tokens = authService.getRevokedRefreshToken({ refreshToken });
return tokens.length > 0;
};
module.exports = {
obtainAccessToken,
refreshAccessToken,

View File

@@ -115,4 +115,22 @@ module.exports = {
foreignKey: { table: ROLES_TABLE, column: tableFields.ID },
},
],
revokedRefreshTokensSchema: [
{
name: tableFields.REFRESH_TOKEN,
type: 'TEXT',
primaryKey: false,
notNull: true,
unique: false,
},
{
name: tableFields.EXPIRES_AT,
type: 'NUMERIC',
primaryKey: false,
notNull: true,
unique: false,
},
],
};

View File

@@ -8,6 +8,7 @@ const {
ROLES_TABLE,
USERS_ROLES_TABLE,
ROLES_PERMISSIONS_TABLE,
REVOKED_REFRESH_TOKENS_TABLE,
tableFields,
} = dbConstants;
@@ -75,5 +76,27 @@ module.exports = () => {
return defaultRole;
},
saveRevokedRefreshToken({ refreshToken, expiresAt }) {
const { lastInsertRowid } = rowService.save({
tableName: REVOKED_REFRESH_TOKENS_TABLE,
fields: {
refresh_token: refreshToken,
expires_at: expiresAt,
},
});
return { id: lastInsertRowid };
},
getRevokedRefreshToken({ refreshToken }) {
const token = rowService.get({
tableName: REVOKED_REFRESH_TOKENS_TABLE,
whereString: `WHERE ${tableFields.REFRESH_TOKEN}=?`,
whereStringValues: [refreshToken],
});
return token;
},
};
};