Add a feature to handle revoked refresh tokens
This commit is contained in:
@@ -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',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user