feat: Use persisted access tokens for authentication

This commit is contained in:
Faruk AYDIN
2024-04-22 16:57:34 +02:00
parent 73c929f25e
commit 6a7cdf2570
47 changed files with 74 additions and 57 deletions

View File

@@ -22,7 +22,7 @@ describe('GET /api/v1/admin/apps/:appKey/auth-clients/:appAuthClientId', () => {
appKey: 'deepl', appKey: 'deepl',
}); });
token = createAuthTokenByUserId(currentUser.id); token = await createAuthTokenByUserId(currentUser.id);
}); });
it('should return specified app auth client', async () => { it('should return specified app auth client', async () => {

View File

@@ -17,7 +17,7 @@ describe('GET /api/v1/admin/apps/:appKey/auth-clients', () => {
adminRole = await createRole({ key: 'admin' }); adminRole = await createRole({ key: 'admin' });
currentUser = await createUser({ roleId: adminRole.id }); currentUser = await createUser({ roleId: adminRole.id });
token = createAuthTokenByUserId(currentUser.id); token = await createAuthTokenByUserId(currentUser.id);
}); });
it('should return specified app auth client info', async () => { it('should return specified app auth client info', async () => {

View File

@@ -14,7 +14,7 @@ describe('GET /api/v1/admin/permissions/catalog', () => {
role = await createRole({ key: 'admin' }); role = await createRole({ key: 'admin' });
currentUser = await createUser({ roleId: role.id }); currentUser = await createUser({ roleId: role.id });
token = createAuthTokenByUserId(currentUser.id); token = await createAuthTokenByUserId(currentUser.id);
}); });
it('should return roles', async () => { it('should return roles', async () => {

View File

@@ -18,7 +18,7 @@ describe('GET /api/v1/admin/roles/:roleId', () => {
permissionTwo = await createPermission({ roleId: role.id }); permissionTwo = await createPermission({ roleId: role.id });
currentUser = await createUser({ roleId: role.id }); currentUser = await createUser({ roleId: role.id });
token = createAuthTokenByUserId(currentUser.id); token = await createAuthTokenByUserId(currentUser.id);
}); });
it('should return role', async () => { it('should return role', async () => {

View File

@@ -15,7 +15,7 @@ describe('GET /api/v1/admin/roles', () => {
roleTwo = await createRole({ key: 'user' }); roleTwo = await createRole({ key: 'user' });
currentUser = await createUser({ roleId: roleOne.id }); currentUser = await createUser({ roleId: roleOne.id });
token = createAuthTokenByUserId(currentUser.id); token = await createAuthTokenByUserId(currentUser.id);
}); });
it('should return roles', async () => { it('should return roles', async () => {

View File

@@ -28,7 +28,7 @@ describe('GET /api/v1/admin/saml-auth-providers/:samlAuthProviderId/role-mapping
remoteRoleName: 'User', remoteRoleName: 'User',
}); });
token = createAuthTokenByUserId(currentUser.id); token = await createAuthTokenByUserId(currentUser.id);
}); });
it('should return role mappings', async () => { it('should return role mappings', async () => {

View File

@@ -17,7 +17,7 @@ describe('GET /api/v1/admin/saml-auth-provider/:samlAuthProviderId', () => {
currentUser = await createUser({ roleId: role.id }); currentUser = await createUser({ roleId: role.id });
samlAuthProvider = await createSamlAuthProvider(); samlAuthProvider = await createSamlAuthProvider();
token = createAuthTokenByUserId(currentUser.id); token = await createAuthTokenByUserId(currentUser.id);
}); });
it('should return saml auth provider with specified id', async () => { it('should return saml auth provider with specified id', async () => {

View File

@@ -18,7 +18,7 @@ describe('GET /api/v1/admin/saml-auth-providers', () => {
samlAuthProviderOne = await createSamlAuthProvider(); samlAuthProviderOne = await createSamlAuthProvider();
samlAuthProviderTwo = await createSamlAuthProvider(); samlAuthProviderTwo = await createSamlAuthProvider();
token = createAuthTokenByUserId(currentUser.id); token = await createAuthTokenByUserId(currentUser.id);
}); });
it('should return saml auth providers', async () => { it('should return saml auth providers', async () => {

View File

@@ -18,7 +18,7 @@ describe('GET /api/v1/admin/users/:userId', () => {
anotherUser = await createUser(); anotherUser = await createUser();
anotherUserRole = await anotherUser.$relatedQuery('role'); anotherUserRole = await anotherUser.$relatedQuery('role');
token = createAuthTokenByUserId(currentUser.id); token = await createAuthTokenByUserId(currentUser.id);
}); });
it('should return specified user info', async () => { it('should return specified user info', async () => {

View File

@@ -28,7 +28,7 @@ describe('GET /api/v1/admin/users', () => {
fullName: 'Another User', fullName: 'Another User',
}); });
token = createAuthTokenByUserId(currentUser.id); token = await createAuthTokenByUserId(currentUser.id);
}); });
it('should return users data', async () => { it('should return users data', async () => {

View File

@@ -11,7 +11,7 @@ describe('GET /api/v1/apps/:appKey/actions/:actionKey/substeps', () => {
beforeEach(async () => { beforeEach(async () => {
currentUser = await createUser(); currentUser = await createUser();
token = createAuthTokenByUserId(currentUser.id); token = await createAuthTokenByUserId(currentUser.id);
exampleApp = await App.findOneByKey('github'); exampleApp = await App.findOneByKey('github');
}); });

View File

@@ -11,7 +11,7 @@ describe('GET /api/v1/apps/:appKey/actions', () => {
beforeEach(async () => { beforeEach(async () => {
currentUser = await createUser(); currentUser = await createUser();
token = createAuthTokenByUserId(currentUser.id); token = await createAuthTokenByUserId(currentUser.id);
}); });
it('should return the app actions', async () => { it('should return the app actions', async () => {

View File

@@ -11,7 +11,7 @@ describe('GET /api/v1/apps/:appKey', () => {
beforeEach(async () => { beforeEach(async () => {
currentUser = await createUser(); currentUser = await createUser();
token = createAuthTokenByUserId(currentUser.id); token = await createAuthTokenByUserId(currentUser.id);
}); });
it('should return the app info', async () => { it('should return the app info', async () => {

View File

@@ -11,7 +11,7 @@ describe('GET /api/v1/apps', () => {
beforeEach(async () => { beforeEach(async () => {
currentUser = await createUser(); currentUser = await createUser();
token = createAuthTokenByUserId(currentUser.id); token = await createAuthTokenByUserId(currentUser.id);
apps = await App.findAll(); apps = await App.findAll();
}); });

View File

@@ -19,7 +19,7 @@ describe('GET /api/v1/apps/:appKey/auth-clients/:appAuthClientId', () => {
appKey: 'deepl', appKey: 'deepl',
}); });
token = createAuthTokenByUserId(currentUser.id); token = await createAuthTokenByUserId(currentUser.id);
}); });
it('should return specified app auth client', async () => { it('should return specified app auth client', async () => {

View File

@@ -15,7 +15,7 @@ describe('GET /api/v1/apps/:appKey/auth-clients', () => {
currentUser = await createUser(); currentUser = await createUser();
token = createAuthTokenByUserId(currentUser.id); token = await createAuthTokenByUserId(currentUser.id);
}); });
it('should return specified app auth client info', async () => { it('should return specified app auth client info', async () => {

View File

@@ -11,7 +11,7 @@ describe('GET /api/v1/apps/:appKey/auth', () => {
beforeEach(async () => { beforeEach(async () => {
currentUser = await createUser(); currentUser = await createUser();
token = createAuthTokenByUserId(currentUser.id); token = await createAuthTokenByUserId(currentUser.id);
}); });
it('should return the app auth info', async () => { it('should return the app auth info', async () => {

View File

@@ -22,7 +22,7 @@ describe('GET /api/v1/apps/:appKey/config', () => {
disabled: false, disabled: false,
}); });
token = createAuthTokenByUserId(currentUser.id); token = await createAuthTokenByUserId(currentUser.id);
}); });
it('should return specified app config info', async () => { it('should return specified app config info', async () => {

View File

@@ -14,7 +14,7 @@ describe('GET /api/v1/apps/:appKey/connections', () => {
currentUser = await createUser(); currentUser = await createUser();
currentUserRole = await currentUser.$relatedQuery('role'); currentUserRole = await currentUser.$relatedQuery('role');
token = createAuthTokenByUserId(currentUser.id); token = await createAuthTokenByUserId(currentUser.id);
}); });
it('should return the connections data of specified app for current user', async () => { it('should return the connections data of specified app for current user', async () => {

View File

@@ -15,7 +15,7 @@ describe('GET /api/v1/apps/:appKey/flows', () => {
currentUser = await createUser(); currentUser = await createUser();
currentUserRole = await currentUser.$relatedQuery('role'); currentUserRole = await currentUser.$relatedQuery('role');
token = createAuthTokenByUserId(currentUser.id); token = await createAuthTokenByUserId(currentUser.id);
}); });
it('should return the flows data of specified app for current user', async () => { it('should return the flows data of specified app for current user', async () => {

View File

@@ -11,7 +11,7 @@ describe('GET /api/v1/apps/:appKey/triggers/:triggerKey/substeps', () => {
beforeEach(async () => { beforeEach(async () => {
currentUser = await createUser(); currentUser = await createUser();
token = createAuthTokenByUserId(currentUser.id); token = await createAuthTokenByUserId(currentUser.id);
exampleApp = await App.findOneByKey('github'); exampleApp = await App.findOneByKey('github');
}); });

View File

@@ -11,7 +11,7 @@ describe('GET /api/v1/apps/:appKey/triggers', () => {
beforeEach(async () => { beforeEach(async () => {
currentUser = await createUser(); currentUser = await createUser();
token = createAuthTokenByUserId(currentUser.id); token = await createAuthTokenByUserId(currentUser.id);
}); });
it('should return the app triggers', async () => { it('should return the app triggers', async () => {

View File

@@ -14,7 +14,7 @@ describe('POST /api/v1/connections/:connectionId/test', () => {
currentUser = await createUser(); currentUser = await createUser();
currentUserRole = await currentUser.$relatedQuery('role'); currentUserRole = await currentUser.$relatedQuery('role');
token = createAuthTokenByUserId(currentUser.id); token = await createAuthTokenByUserId(currentUser.id);
}); });
it('should update the connection as not verified for current user', async () => { it('should update the connection as not verified for current user', async () => {

View File

@@ -16,7 +16,7 @@ describe('GET /api/v1/connections/:connectionId/flows', () => {
currentUser = await createUser(); currentUser = await createUser();
currentUserRole = await currentUser.$relatedQuery('role'); currentUserRole = await currentUser.$relatedQuery('role');
token = createAuthTokenByUserId(currentUser.id); token = await createAuthTokenByUserId(currentUser.id);
}); });
it('should return the flows data of specified connection for current user', async () => { it('should return the flows data of specified connection for current user', async () => {

View File

@@ -20,7 +20,7 @@ describe('GET /api/v1/executions/:executionId/execution-steps', () => {
anotherUser = await createUser(); anotherUser = await createUser();
token = createAuthTokenByUserId(currentUser.id); token = await createAuthTokenByUserId(currentUser.id);
}); });
it('should return the execution steps of current user execution', async () => { it('should return the execution steps of current user execution', async () => {

View File

@@ -17,7 +17,7 @@ describe('GET /api/v1/executions/:executionId', () => {
currentUser = await createUser(); currentUser = await createUser();
currentUserRole = await currentUser.$relatedQuery('role'); currentUserRole = await currentUser.$relatedQuery('role');
token = createAuthTokenByUserId(currentUser.id); token = await createAuthTokenByUserId(currentUser.id);
}); });
it('should return the execution data of current user', async () => { it('should return the execution data of current user', async () => {

View File

@@ -18,7 +18,7 @@ describe('GET /api/v1/executions', () => {
anotherUser = await createUser(); anotherUser = await createUser();
token = createAuthTokenByUserId(currentUser.id); token = await createAuthTokenByUserId(currentUser.id);
}); });
it('should return the executions of current user', async () => { it('should return the executions of current user', async () => {

View File

@@ -16,7 +16,7 @@ describe('GET /api/v1/flows/:flowId', () => {
currentUser = await createUser(); currentUser = await createUser();
currentUserRole = await currentUser.$relatedQuery('role'); currentUserRole = await currentUser.$relatedQuery('role');
token = createAuthTokenByUserId(currentUser.id); token = await createAuthTokenByUserId(currentUser.id);
}); });
it('should return the flow data of current user', async () => { it('should return the flow data of current user', async () => {

View File

@@ -15,7 +15,7 @@ describe('GET /api/v1/flows', () => {
currentUser = await createUser(); currentUser = await createUser();
currentUserRole = await currentUser.$relatedQuery('role'); currentUserRole = await currentUser.$relatedQuery('role');
token = createAuthTokenByUserId(currentUser.id); token = await createAuthTokenByUserId(currentUser.id);
}); });
it('should return the flows data of current user', async () => { it('should return the flows data of current user', async () => {

View File

@@ -12,7 +12,7 @@ describe('GET /api/v1/payment/paddle-info', () => {
beforeEach(async () => { beforeEach(async () => {
user = await createUser(); user = await createUser();
token = createAuthTokenByUserId(user.id); token = await createAuthTokenByUserId(user.id);
vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(true); vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(true);
vi.spyOn(billing.paddleInfo, 'vendorId', 'get').mockReturnValue( vi.spyOn(billing.paddleInfo, 'vendorId', 'get').mockReturnValue(

View File

@@ -11,7 +11,7 @@ describe('GET /api/v1/payment/plans', () => {
beforeEach(async () => { beforeEach(async () => {
user = await createUser(); user = await createUser();
token = createAuthTokenByUserId(user.id); token = await createAuthTokenByUserId(user.id);
vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(true); vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(true);
}); });

View File

@@ -18,7 +18,7 @@ describe('POST /api/v1/steps/:stepId/dynamic-data', () => {
currentUser = await createUser(); currentUser = await createUser();
currentUserRole = await currentUser.$relatedQuery('role'); currentUserRole = await currentUser.$relatedQuery('role');
token = createAuthTokenByUserId(currentUser.id); token = await createAuthTokenByUserId(currentUser.id);
}); });
describe('should return dynamically created data', () => { describe('should return dynamically created data', () => {

View File

@@ -16,7 +16,7 @@ describe('POST /api/v1/steps/:stepId/dynamic-fields', () => {
currentUser = await createUser(); currentUser = await createUser();
currentUserRole = await currentUser.$relatedQuery('role'); currentUserRole = await currentUser.$relatedQuery('role');
token = createAuthTokenByUserId(currentUser.id); token = await createAuthTokenByUserId(currentUser.id);
}); });
it('should return dynamically created fields of the current users step', async () => { it('should return dynamically created fields of the current users step', async () => {

View File

@@ -17,7 +17,7 @@ describe('GET /api/v1/steps/:stepId/connection', () => {
currentUser = await createUser(); currentUser = await createUser();
currentUserRole = await currentUser.$relatedQuery('role'); currentUserRole = await currentUser.$relatedQuery('role');
token = createAuthTokenByUserId(currentUser.id); token = await createAuthTokenByUserId(currentUser.id);
}); });
it('should return the current user connection data of specified step', async () => { it('should return the current user connection data of specified step', async () => {

View File

@@ -17,7 +17,7 @@ describe('GET /api/v1/steps/:stepId/previous-steps', () => {
currentUser = await createUser(); currentUser = await createUser();
currentUserRole = await currentUser.$relatedQuery('role'); currentUserRole = await currentUser.$relatedQuery('role');
token = createAuthTokenByUserId(currentUser.id); token = await createAuthTokenByUserId(currentUser.id);
}); });
it('should return the previous steps of the specified step of the current user', async () => { it('should return the previous steps of the specified step of the current user', async () => {

View File

@@ -17,7 +17,7 @@ describe('GET /api/v1/users/:userId/apps', () => {
currentUserRole = await createRole(); currentUserRole = await createRole();
currentUser = await createUser({ roleId: currentUserRole.id }); currentUser = await createUser({ roleId: currentUserRole.id });
token = createAuthTokenByUserId(currentUser.id); token = await createAuthTokenByUserId(currentUser.id);
}); });
it('should return all apps of the current user', async () => { it('should return all apps of the current user', async () => {

View File

@@ -25,7 +25,7 @@ describe('GET /api/v1/users/me', () => {
roleId: role.id, roleId: role.id,
}); });
token = createAuthTokenByUserId(currentUser.id); token = await createAuthTokenByUserId(currentUser.id);
}); });
it('should return current user info', async () => { it('should return current user info', async () => {

View File

@@ -11,7 +11,7 @@ describe('GET /api/v1/user/invoices', () => {
beforeEach(async () => { beforeEach(async () => {
currentUser = await createUser(); currentUser = await createUser();
token = createAuthTokenByUserId(currentUser.id); token = await createAuthTokenByUserId(currentUser.id);
}); });
it('should return current user invoices', async () => { it('should return current user invoices', async () => {

View File

@@ -14,7 +14,7 @@ describe('GET /api/v1/users/:userId/plan-and-usage', () => {
beforeEach(async () => { beforeEach(async () => {
const trialExpiryDate = DateTime.now().plus({ days: 30 }).toISODate(); const trialExpiryDate = DateTime.now().plus({ days: 30 }).toISODate();
user = await createUser({ trialExpiryDate }); user = await createUser({ trialExpiryDate });
token = createAuthTokenByUserId(user.id); token = await createAuthTokenByUserId(user.id);
vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(true); vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(true);
}); });

View File

@@ -22,7 +22,7 @@ describe('GET /api/v1/users/:userId/subscription', () => {
subscription = await createSubscription({ userId: currentUser.id }); subscription = await createSubscription({ userId: currentUser.id });
token = createAuthTokenByUserId(currentUser.id); token = await createAuthTokenByUserId(currentUser.id);
}); });
it('should return subscription info of the current user', async () => { it('should return subscription info of the current user', async () => {
@@ -41,7 +41,7 @@ describe('GET /api/v1/users/:userId/subscription', () => {
roleId: role.id, roleId: role.id,
}); });
const token = createAuthTokenByUserId(userWithoutSubscription.id); const token = await createAuthTokenByUserId(userWithoutSubscription.id);
await request(app) await request(app)
.get(`/api/v1/users/${userWithoutSubscription.id}/subscription`) .get(`/api/v1/users/${userWithoutSubscription.id}/subscription`)

View File

@@ -14,7 +14,7 @@ describe('GET /api/v1/users/:userId/trial', () => {
beforeEach(async () => { beforeEach(async () => {
const trialExpiryDate = DateTime.now().plus({ days: 30 }).toISODate(); const trialExpiryDate = DateTime.now().plus({ days: 30 }).toISODate();
user = await createUser({ trialExpiryDate }); user = await createUser({ trialExpiryDate });
token = createAuthTokenByUserId(user.id); token = await createAuthTokenByUserId(user.id);
vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(true); vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(true);
}); });

View File

@@ -7,7 +7,7 @@ const login = async (_parent, params) => {
}); });
if (user && (await user.login(params.input.password))) { if (user && (await user.login(params.input.password))) {
const token = createAuthTokenByUserId(user.id); const token = await createAuthTokenByUserId(user.id);
return { token, user }; return { token, user };
} }

View File

@@ -1,7 +1,6 @@
import { allow, rule, shield } from 'graphql-shield'; import { allow, rule, shield } from 'graphql-shield';
import jwt from 'jsonwebtoken';
import appConfig from '../config/app.js';
import User from '../models/user.js'; import User from '../models/user.js';
import AccessToken from '../models/access-token.js';
export const isAuthenticated = async (_parent, _args, req) => { export const isAuthenticated = async (_parent, _args, req) => {
const token = req.headers['authorization']; const token = req.headers['authorization'];
@@ -9,10 +8,22 @@ export const isAuthenticated = async (_parent, _args, req) => {
if (token == null) return false; if (token == null) return false;
try { try {
const { userId } = jwt.verify(token, appConfig.appSecretKey); const accessToken = await AccessToken.query().findOne({
token,
revoked_at: null,
});
const expirationTime =
new Date(accessToken.createdAt).getTime() + accessToken.expiresIn * 1000;
if (Date.now() > expirationTime) {
return false;
}
const user = await accessToken.$relatedQuery('user');
req.currentUser = await User.query() req.currentUser = await User.query()
.findById(userId) .findById(user.id)
.leftJoinRelated({ .leftJoinRelated({
role: true, role: true,
permissions: true, permissions: true,

View File

@@ -17,7 +17,7 @@ describe('isAuthenticated', () => {
it('should return true if token is valid and there is a user', async () => { it('should return true if token is valid and there is a user', async () => {
const user = await createUser(); const user = await createUser();
const token = createAuthTokenByUserId(user.id); const token = await createAuthTokenByUserId(user.id);
const req = { headers: { authorization: token } }; const req = { headers: { authorization: token } };
expect(await isAuthenticated(null, null, req)).toBe(true); expect(await isAuthenticated(null, null, req)).toBe(true);
@@ -25,7 +25,7 @@ describe('isAuthenticated', () => {
it('should return false if token is valid and but there is no user', async () => { it('should return false if token is valid and but there is no user', async () => {
const user = await createUser(); const user = await createUser();
const token = createAuthTokenByUserId(user.id); const token = await createAuthTokenByUserId(user.id);
await user.$query().delete(); await user.$query().delete();
const req = { headers: { authorization: token } }; const req = { headers: { authorization: token } };

View File

@@ -1,10 +1,16 @@
import jwt from 'jsonwebtoken'; import crypto from 'crypto';
import appConfig from '../config/app.js'; import User from '../models/user.js';
import AccessToken from '../models/access-token.js';
const TOKEN_EXPIRES_IN = '14d'; const TOKEN_EXPIRES_IN = 14 * 24 * 60 * 60; // 14 days in seconds
const createAuthTokenByUserId = (userId) => { const createAuthTokenByUserId = async (userId) => {
const token = jwt.sign({ userId }, appConfig.appSecretKey, { const user = await User.query().findById(userId).throwIfNotFound();
const token = await crypto.randomBytes(48).toString('hex');
await AccessToken.query().insert({
token,
userId: user.id,
expiresIn: TOKEN_EXPIRES_IN, expiresIn: TOKEN_EXPIRES_IN,
}); });

View File

@@ -76,8 +76,8 @@ export default function configurePassport(app) {
failureRedirect: '/', failureRedirect: '/',
failureFlash: true, failureFlash: true,
}), }),
(req, res) => { async (req, res) => {
const token = createAuthTokenByUserId(req.currentUser.id); const token = await createAuthTokenByUserId(req.currentUser.id);
const redirectUrl = new URL( const redirectUrl = new URL(
`/login/callback?token=${token}`, `/login/callback?token=${token}`,

View File

@@ -185,7 +185,7 @@ class User extends Base {
}); });
if (user && (await user.login(password))) { if (user && (await user.login(password))) {
const token = createAuthTokenByUserId(user.id); const token = await createAuthTokenByUserId(user.id);
return token; return token;
} }
} }