2024-01-24 04:38:57 -08:00
import { Container } from 'typedi' ;
2024-02-08 06:13:29 -08:00
import { In } from '@n8n/typeorm' ;
2022-09-21 01:20:29 -07:00
2024-05-31 00:40:03 -07:00
import config from '@/config' ;
2023-12-07 02:35:40 -08:00
import type { ListQuery } from '@/requests' ;
2023-03-17 09:24:05 -07:00
import type { User } from '@db/entities/User' ;
2024-01-24 04:38:57 -08:00
import { SharedCredentialsRepository } from '@db/repositories/sharedCredentials.repository' ;
2024-05-31 00:40:03 -07:00
import { ProjectRepository } from '@db/repositories/project.repository' ;
import type { Project } from '@db/entities/Project' ;
import { ProjectService } from '@/services/project.service' ;
2024-08-22 02:10:37 -07:00
import { UserManagementMailer } from '@/user-management/email' ;
2023-11-08 07:29:39 -08:00
2024-05-17 01:53:15 -07:00
import { randomCredentialPayload } from '../shared/random' ;
import * as testDb from '../shared/testDb' ;
import type { SaveCredentialFunction } from '../shared/types' ;
import * as utils from '../shared/utils' ;
import {
affixRoleToSaveCredential ,
2024-06-04 04:54:48 -07:00
getCredentialSharings ,
2024-05-17 01:53:15 -07:00
shareCredentialWithProjects ,
shareCredentialWithUsers ,
} from '../shared/db/credentials' ;
2024-06-04 04:54:48 -07:00
import {
createAdmin ,
createManyUsers ,
createOwner ,
createUser ,
createUserShell ,
} from '../shared/db/users' ;
2024-05-31 00:40:03 -07:00
import type { SuperAgentTest } from '../shared/types' ;
2024-05-17 01:53:15 -07:00
import { mockInstance } from '../../shared/mocking' ;
2024-06-04 04:54:48 -07:00
import { createTeamProject , linkUserToProject } from '../shared/db/projects' ;
2024-06-14 05:48:49 -07:00
import { createWorkflow , shareWorkflowWithUsers } from '@test-integration/db/workflows' ;
2024-06-04 04:54:48 -07:00
2024-01-31 00:48:48 -08:00
const testServer = utils . setupTestServer ( {
endpointGroups : [ 'credentials' ] ,
enabledFeatures : [ 'feat:sharing' ] ,
2024-05-17 01:53:15 -07:00
quotas : {
'quota:maxTeamProjects' : - 1 ,
} ,
2024-01-31 00:48:48 -08:00
} ) ;
2022-09-21 01:20:29 -07:00
2023-03-17 09:24:05 -07:00
let owner : User ;
2024-06-04 04:54:48 -07:00
let admin : User ;
2024-05-17 01:53:15 -07:00
let ownerPersonalProject : Project ;
2023-03-17 09:24:05 -07:00
let member : User ;
2024-05-17 01:53:15 -07:00
let memberPersonalProject : Project ;
2023-11-29 08:32:27 -08:00
let anotherMember : User ;
2024-05-17 01:53:15 -07:00
let anotherMemberPersonalProject : Project ;
2023-03-17 09:24:05 -07:00
let authOwnerAgent : SuperAgentTest ;
2024-08-09 03:59:28 -07:00
let authMemberAgent : SuperAgentTest ;
2023-11-29 08:32:27 -08:00
let authAnotherMemberAgent : SuperAgentTest ;
2023-03-17 09:24:05 -07:00
let saveCredential : SaveCredentialFunction ;
2024-01-23 03:03:59 -08:00
const mailer = mockInstance ( UserManagementMailer ) ;
2022-09-21 01:20:29 -07:00
2024-05-17 01:53:15 -07:00
let projectService : ProjectService ;
let projectRepository : ProjectRepository ;
beforeEach ( async ( ) = > {
await testDb . truncate ( [ 'SharedCredentials' , 'Credentials' , 'Project' , 'ProjectRelation' ] ) ;
projectRepository = Container . get ( ProjectRepository ) ;
projectService = Container . get ( ProjectService ) ;
2024-06-04 04:54:48 -07:00
owner = await createOwner ( ) ;
admin = await createAdmin ( ) ;
2024-05-17 01:53:15 -07:00
ownerPersonalProject = await projectRepository . getPersonalProjectForUserOrFail ( owner . id ) ;
2024-01-24 04:38:57 -08:00
member = await createUser ( { role : 'global:member' } ) ;
2024-05-17 01:53:15 -07:00
memberPersonalProject = await projectRepository . getPersonalProjectForUserOrFail ( member . id ) ;
2024-01-24 04:38:57 -08:00
anotherMember = await createUser ( { role : 'global:member' } ) ;
2024-05-17 01:53:15 -07:00
anotherMemberPersonalProject = await projectRepository . getPersonalProjectForUserOrFail (
anotherMember . id ,
) ;
2022-09-21 01:20:29 -07:00
2023-07-13 01:14:48 -07:00
authOwnerAgent = testServer . authAgentFor ( owner ) ;
2024-08-09 03:59:28 -07:00
authMemberAgent = testServer . authAgentFor ( member ) ;
2023-11-29 08:32:27 -08:00
authAnotherMemberAgent = testServer . authAgentFor ( anotherMember ) ;
2022-09-21 01:20:29 -07:00
2024-01-24 04:38:57 -08:00
saveCredential = affixRoleToSaveCredential ( 'credential:owner' ) ;
2022-09-21 01:20:29 -07:00
} ) ;
2024-01-23 03:03:59 -08:00
afterEach ( ( ) = > {
jest . clearAllMocks ( ) ;
} ) ;
2024-06-06 02:55:48 -07:00
describe ( 'POST /credentials' , ( ) = > {
test ( 'project viewers cannot create credentials' , async ( ) = > {
const teamProject = await createTeamProject ( ) ;
await linkUserToProject ( member , teamProject , 'project:viewer' ) ;
const response = await testServer
. authAgentFor ( member )
. post ( '/credentials' )
. send ( { . . . randomCredentialPayload ( ) , projectId : teamProject.id } ) ;
expect ( response . statusCode ) . toBe ( 400 ) ;
expect ( response . body . message ) . toBe (
"You don't have the permissions to save the credential in this project." ,
) ;
} ) ;
} ) ;
2022-09-21 01:20:29 -07:00
// ----------------------------------------
// GET /credentials - fetch all credentials
// ----------------------------------------
2023-03-17 09:24:05 -07:00
describe ( 'GET /credentials' , ( ) = > {
test ( 'should return all creds for owner' , async ( ) = > {
2023-11-08 07:29:39 -08:00
const [ member1 , member2 , member3 ] = await createManyUsers ( 3 , {
2024-01-24 04:38:57 -08:00
role : 'global:member' ,
2023-03-17 09:24:05 -07:00
} ) ;
2024-05-17 01:53:15 -07:00
const member1PersonalProject = await projectRepository . getPersonalProjectForUserOrFail (
member1 . id ,
) ;
const member2PersonalProject = await projectRepository . getPersonalProjectForUserOrFail (
member2 . id ,
) ;
const member3PersonalProject = await projectRepository . getPersonalProjectForUserOrFail (
member3 . id ,
) ;
2022-09-21 01:20:29 -07:00
2023-03-17 09:24:05 -07:00
const savedCredential = await saveCredential ( randomCredentialPayload ( ) , { user : owner } ) ;
await saveCredential ( randomCredentialPayload ( ) , { user : member1 } ) ;
2022-09-21 01:20:29 -07:00
2024-05-17 01:53:15 -07:00
const sharedWith = [ member1PersonalProject , member2PersonalProject , member3PersonalProject ] ;
await shareCredentialWithProjects ( savedCredential , sharedWith ) ;
2022-09-21 01:20:29 -07:00
2023-03-17 09:24:05 -07:00
const response = await authOwnerAgent . get ( '/credentials' ) ;
2022-09-21 01:20:29 -07:00
2023-03-17 09:24:05 -07:00
expect ( response . statusCode ) . toBe ( 200 ) ;
expect ( response . body . data ) . toHaveLength ( 2 ) ; // owner retrieved owner cred and member cred
2024-05-17 01:53:15 -07:00
const ownerCredential : ListQuery.Credentials.WithOwnedByAndSharedWith = response . body . data . find (
( e : ListQuery.Credentials.WithOwnedByAndSharedWith ) = >
e . homeProject ? . id === ownerPersonalProject . id ,
2023-06-29 05:48:55 -07:00
) ;
2024-05-17 01:53:15 -07:00
const memberCredential : ListQuery.Credentials.WithOwnedByAndSharedWith =
response . body . data . find (
( e : ListQuery.Credentials.WithOwnedByAndSharedWith ) = >
e . homeProject ? . id === member1PersonalProject . id ,
) ;
2022-09-21 01:20:29 -07:00
2023-03-17 09:24:05 -07:00
validateMainCredentialData ( ownerCredential ) ;
expect ( ownerCredential . data ) . toBeUndefined ( ) ;
2022-09-21 01:20:29 -07:00
2023-03-17 09:24:05 -07:00
validateMainCredentialData ( memberCredential ) ;
expect ( memberCredential . data ) . toBeUndefined ( ) ;
2022-09-21 01:20:29 -07:00
2024-05-17 01:53:15 -07:00
expect ( ownerCredential . homeProject ) . toMatchObject ( {
id : ownerPersonalProject.id ,
type : 'personal' ,
name : owner.createPersonalProjectName ( ) ,
2023-03-17 09:24:05 -07:00
} ) ;
2022-09-21 01:20:29 -07:00
2024-05-17 01:53:15 -07:00
expect ( Array . isArray ( ownerCredential . sharedWithProjects ) ) . toBe ( true ) ;
expect ( ownerCredential . sharedWithProjects ) . toHaveLength ( 3 ) ;
2023-03-17 09:24:05 -07:00
// Fix order issue (MySQL might return items in any order)
2024-05-17 01:53:15 -07:00
const ownerCredentialsSharedWithOrdered = [ . . . ownerCredential . sharedWithProjects ] . sort (
( a , b ) = > ( a . id < b . id ? - 1 : 1 ) ,
2023-03-17 09:24:05 -07:00
) ;
2024-05-17 01:53:15 -07:00
const orderedSharedWith = [ . . . sharedWith ] . sort ( ( a , b ) = > ( a . id < b . id ? - 1 : 1 ) ) ;
2023-03-17 09:24:05 -07:00
2024-05-17 01:53:15 -07:00
ownerCredentialsSharedWithOrdered . forEach ( ( sharee , idx ) = > {
2023-03-17 09:24:05 -07:00
expect ( sharee ) . toMatchObject ( {
id : orderedSharedWith [ idx ] . id ,
2024-05-17 01:53:15 -07:00
type : orderedSharedWith [ idx ] . type ,
2023-03-17 09:24:05 -07:00
} ) ;
} ) ;
2022-10-11 05:55:05 -07:00
2024-05-17 01:53:15 -07:00
expect ( memberCredential . homeProject ) . toMatchObject ( {
id : member1PersonalProject.id ,
type : member1PersonalProject . type ,
name : member1.createPersonalProjectName ( ) ,
2022-09-21 01:20:29 -07:00
} ) ;
2024-05-17 01:53:15 -07:00
expect ( Array . isArray ( memberCredential . sharedWithProjects ) ) . toBe ( true ) ;
expect ( memberCredential . sharedWithProjects ) . toHaveLength ( 0 ) ;
2022-09-21 01:20:29 -07:00
} ) ;
2023-03-17 09:24:05 -07:00
test ( 'should return only relevant creds for member' , async ( ) = > {
2023-11-08 07:29:39 -08:00
const [ member1 , member2 ] = await createManyUsers ( 2 , {
2024-01-24 04:38:57 -08:00
role : 'global:member' ,
2023-03-17 09:24:05 -07:00
} ) ;
2024-05-17 01:53:15 -07:00
const member1PersonalProject = await projectRepository . getPersonalProjectForUserOrFail (
member1 . id ,
) ;
const member2PersonalProject = await projectRepository . getPersonalProjectForUserOrFail (
member2 . id ,
) ;
2022-09-21 01:20:29 -07:00
2023-03-17 09:24:05 -07:00
await saveCredential ( randomCredentialPayload ( ) , { user : member2 } ) ;
const savedMemberCredential = await saveCredential ( randomCredentialPayload ( ) , {
user : member1 ,
} ) ;
2022-09-21 01:20:29 -07:00
2023-11-08 07:29:39 -08:00
await shareCredentialWithUsers ( savedMemberCredential , [ member2 ] ) ;
2022-09-21 01:20:29 -07:00
2023-07-13 01:14:48 -07:00
const response = await testServer . authAgentFor ( member1 ) . get ( '/credentials' ) ;
2022-09-21 01:20:29 -07:00
2023-03-17 09:24:05 -07:00
expect ( response . statusCode ) . toBe ( 200 ) ;
expect ( response . body . data ) . toHaveLength ( 1 ) ; // member retrieved only member cred
2022-09-21 01:20:29 -07:00
2024-05-17 01:53:15 -07:00
const [ member1Credential ] : [ ListQuery . Credentials . WithOwnedByAndSharedWith ] =
response . body . data ;
2022-09-21 01:20:29 -07:00
2023-03-17 09:24:05 -07:00
validateMainCredentialData ( member1Credential ) ;
expect ( member1Credential . data ) . toBeUndefined ( ) ;
2022-09-21 01:20:29 -07:00
2024-05-17 01:53:15 -07:00
expect ( member1Credential . homeProject ) . toMatchObject ( {
id : member1PersonalProject.id ,
name : member1.createPersonalProjectName ( ) ,
type : member1PersonalProject . type ,
2023-03-17 09:24:05 -07:00
} ) ;
2022-09-21 01:20:29 -07:00
2024-05-17 01:53:15 -07:00
expect ( member1Credential . sharedWithProjects ) . toHaveLength ( 1 ) ;
expect ( member1Credential . sharedWithProjects [ 0 ] ) . toMatchObject ( {
id : member2PersonalProject.id ,
name : member2.createPersonalProjectName ( ) ,
type : member2PersonalProject . type ,
2023-03-17 09:24:05 -07:00
} ) ;
2022-09-21 01:20:29 -07:00
} ) ;
2024-05-17 01:53:15 -07:00
test ( 'should show credentials that the user has access to through a team project they are part of' , async ( ) = > {
//
// ARRANGE
//
const project1 = await projectService . createTeamProject ( 'Team Project' , member ) ;
await projectService . addUser ( project1 . id , anotherMember . id , 'project:editor' ) ;
// anotherMember should see this one
const credential1 = await saveCredential ( randomCredentialPayload ( ) , { project : project1 } ) ;
const project2 = await projectService . createTeamProject ( 'Team Project' , member ) ;
// anotherMember should NOT see this one
await saveCredential ( randomCredentialPayload ( ) , { project : project2 } ) ;
//
// ACT
//
const response = await testServer . authAgentFor ( anotherMember ) . get ( '/credentials' ) ;
//
// ASSERT
//
expect ( response . body . data ) . toHaveLength ( 1 ) ;
expect ( response . body . data [ 0 ] . id ) . toBe ( credential1 . id ) ;
} ) ;
2022-09-21 01:20:29 -07:00
} ) ;
2024-06-14 05:48:49 -07:00
describe ( 'GET /credentials/for-workflow' , ( ) = > {
describe ( 'for team projects' , ( ) = > {
test . each ( [
[ 'workflowId' , 'member' , ( ) = > member ] ,
[ 'projectId' , 'member' , ( ) = > member ] ,
[ 'workflowId' , 'owner' , ( ) = > owner ] ,
[ 'projectId' , 'owner' , ( ) = > owner ] ,
] ) (
'it will only return the credentials in that project if "%s" is used as the query parameter and the actor is a "%s"' ,
async ( _ , queryParam , actorGetter ) = > {
const actor = actorGetter ( ) ;
// Credential in personal project that should not be returned
await saveCredential ( randomCredentialPayload ( ) , { user : actor } ) ;
const teamProject = await createTeamProject ( ) ;
await linkUserToProject ( actor , teamProject , 'project:viewer' ) ;
const savedCredential = await saveCredential ( randomCredentialPayload ( ) , {
project : teamProject ,
} ) ;
const savedWorkflow = await createWorkflow ( { } , teamProject ) ;
{
const response = await testServer
. authAgentFor ( actor )
. get ( '/credentials/for-workflow' )
. query (
queryParam === 'workflowId'
? { workflowId : savedWorkflow.id }
: { projectId : teamProject.id } ,
) ;
expect ( response . statusCode ) . toBe ( 200 ) ;
expect ( response . body . data ) . toHaveLength ( 1 ) ;
expect ( response . body . data ) . toContainEqual (
expect . objectContaining ( {
id : savedCredential.id ,
scopes : expect.arrayContaining ( [ 'credential:read' ] ) ,
} ) ,
) ;
}
} ,
) ;
} ) ;
describe ( 'for personal projects' , ( ) = > {
test . each ( [ 'projectId' , 'workflowId' ] ) (
'it returns only personal credentials for a members, if "%s" is used as the query parameter' ,
async ( queryParam ) = > {
const savedWorkflow = await createWorkflow ( { } , member ) ;
await shareWorkflowWithUsers ( savedWorkflow , [ anotherMember ] ) ;
// should be returned respectively
const savedCredential = await saveCredential ( randomCredentialPayload ( ) , { user : member } ) ;
const anotherSavedCredential = await saveCredential ( randomCredentialPayload ( ) , {
user : anotherMember ,
} ) ;
// should not be returned
await saveCredential ( randomCredentialPayload ( ) , { user : owner } ) ;
const teamProject = await createTeamProject ( ) ;
await saveCredential ( randomCredentialPayload ( ) , { project : teamProject } ) ;
// member should only see their credential
{
const response = await testServer
. authAgentFor ( member )
. get ( '/credentials/for-workflow' )
. query (
queryParam === 'workflowId'
? { workflowId : savedWorkflow.id }
: { projectId : memberPersonalProject.id } ,
) ;
expect ( response . statusCode ) . toBe ( 200 ) ;
expect ( response . body . data ) . toHaveLength ( 1 ) ;
expect ( response . body . data ) . toContainEqual (
expect . objectContaining ( {
id : savedCredential.id ,
scopes : expect.arrayContaining ( [ 'credential:read' ] ) ,
} ) ,
) ;
}
// another member should only see their credential
{
const response = await testServer
. authAgentFor ( anotherMember )
. get ( '/credentials/for-workflow' )
. query (
queryParam === 'workflowId'
? { workflowId : savedWorkflow.id }
: { projectId : anotherMemberPersonalProject.id } ,
) ;
expect ( response . statusCode ) . toBe ( 200 ) ;
expect ( response . body . data ) . toHaveLength ( 1 ) ;
expect ( response . body . data ) . toContainEqual (
expect . objectContaining ( {
id : anotherSavedCredential.id ,
scopes : expect.arrayContaining ( [ 'credential:read' ] ) ,
} ) ,
) ;
}
} ,
) ;
test ( 'if the actor is a global owner and the workflow has not been shared with them, it returns all credentials of all projects the workflow is part of' , async ( ) = > {
const memberWorkflow = await createWorkflow ( { } , member ) ;
await shareWorkflowWithUsers ( memberWorkflow , [ owner ] ) ;
// should be returned
const memberCredential = await saveCredential ( randomCredentialPayload ( ) , { user : member } ) ;
const ownerCredential = await saveCredential ( randomCredentialPayload ( ) , { user : owner } ) ;
// should not be returned
await saveCredential ( randomCredentialPayload ( ) , { user : anotherMember } ) ;
const teamProject = await createTeamProject ( ) ;
await saveCredential ( randomCredentialPayload ( ) , { project : teamProject } ) ;
const response = await testServer
. authAgentFor ( owner )
. get ( '/credentials/for-workflow' )
. query ( { workflowId : memberWorkflow.id } ) ;
expect ( response . statusCode ) . toBe ( 200 ) ;
expect ( response . body . data ) . toHaveLength ( 2 ) ;
expect ( response . body . data ) . toContainEqual (
expect . objectContaining ( {
id : memberCredential.id ,
scopes : expect.arrayContaining ( [ 'credential:read' ] ) ,
} ) ,
) ;
expect ( response . body . data ) . toContainEqual (
expect . objectContaining ( {
id : ownerCredential.id ,
scopes : expect.arrayContaining ( [ 'credential:read' ] ) ,
} ) ,
) ;
} ) ;
test ( 'if the actor is a global owner and the workflow has been shared with them, it returns all credentials of all projects the workflow is part of' , async ( ) = > {
const memberWorkflow = await createWorkflow ( { } , member ) ;
await shareWorkflowWithUsers ( memberWorkflow , [ anotherMember ] ) ;
// should be returned
const memberCredential = await saveCredential ( randomCredentialPayload ( ) , { user : member } ) ;
const anotherMemberCredential = await saveCredential ( randomCredentialPayload ( ) , {
user : anotherMember ,
} ) ;
// should not be returned
await saveCredential ( randomCredentialPayload ( ) , { user : owner } ) ;
const teamProject = await createTeamProject ( ) ;
await saveCredential ( randomCredentialPayload ( ) , { project : teamProject } ) ;
const response = await testServer
. authAgentFor ( owner )
. get ( '/credentials/for-workflow' )
. query ( { workflowId : memberWorkflow.id } ) ;
expect ( response . statusCode ) . toBe ( 200 ) ;
expect ( response . body . data ) . toHaveLength ( 2 ) ;
expect ( response . body . data ) . toContainEqual (
expect . objectContaining ( {
id : memberCredential.id ,
scopes : expect.arrayContaining ( [ 'credential:read' ] ) ,
} ) ,
) ;
expect ( response . body . data ) . toContainEqual (
expect . objectContaining ( {
id : anotherMemberCredential.id ,
scopes : expect.arrayContaining ( [ 'credential:read' ] ) ,
} ) ,
) ;
} ) ;
test ( 'if the projectId is passed by a global owner it will return all credentials in that project' , async ( ) = > {
// should be returned
const memberCredential = await saveCredential ( randomCredentialPayload ( ) , { user : member } ) ;
// should not be returned
await saveCredential ( randomCredentialPayload ( ) , { user : anotherMember } ) ;
await saveCredential ( randomCredentialPayload ( ) , { user : owner } ) ;
const teamProject = await createTeamProject ( ) ;
await saveCredential ( randomCredentialPayload ( ) , { project : teamProject } ) ;
const response = await testServer
. authAgentFor ( owner )
. get ( '/credentials/for-workflow' )
. query ( { projectId : memberPersonalProject.id } ) ;
expect ( response . statusCode ) . toBe ( 200 ) ;
expect ( response . body . data ) . toHaveLength ( 1 ) ;
expect ( response . body . data ) . toContainEqual (
expect . objectContaining ( {
id : memberCredential.id ,
scopes : expect.arrayContaining ( [ 'credential:read' ] ) ,
} ) ,
) ;
} ) ;
test . each ( [ 'workflowId' , 'projectId' ] as const ) (
'if the global owner owns the workflow it will return all credentials of all personal projects, when using "%s" as the query parameter' ,
async ( queryParam ) = > {
const ownerWorkflow = await createWorkflow ( { } , owner ) ;
// should be returned
const memberCredential = await saveCredential ( randomCredentialPayload ( ) , { user : member } ) ;
const anotherMemberCredential = await saveCredential ( randomCredentialPayload ( ) , {
user : anotherMember ,
} ) ;
const ownerCredential = await saveCredential ( randomCredentialPayload ( ) , { user : owner } ) ;
// should not be returned
const teamProject = await createTeamProject ( ) ;
await saveCredential ( randomCredentialPayload ( ) , { project : teamProject } ) ;
const response = await testServer
. authAgentFor ( owner )
. get ( '/credentials/for-workflow' )
. query (
queryParam === 'workflowId'
? { workflowId : ownerWorkflow.id }
: { projectId : ownerPersonalProject.id } ,
) ;
expect ( response . statusCode ) . toBe ( 200 ) ;
expect ( response . body . data ) . toHaveLength ( 3 ) ;
expect ( response . body . data ) . toContainEqual (
expect . objectContaining ( {
id : memberCredential.id ,
scopes : expect.arrayContaining ( [ 'credential:read' ] ) ,
} ) ,
) ;
expect ( response . body . data ) . toContainEqual (
expect . objectContaining ( {
id : anotherMemberCredential.id ,
scopes : expect.arrayContaining ( [ 'credential:read' ] ) ,
} ) ,
) ;
expect ( response . body . data ) . toContainEqual (
expect . objectContaining ( {
id : ownerCredential.id ,
scopes : expect.arrayContaining ( [ 'credential:read' ] ) ,
} ) ,
) ;
} ,
) ;
} ) ;
} ) ;
2022-09-21 01:20:29 -07:00
// ----------------------------------------
// GET /credentials/:id - fetch a certain credential
// ----------------------------------------
2023-03-17 09:24:05 -07:00
describe ( 'GET /credentials/:id' , ( ) = > {
2024-06-06 02:55:48 -07:00
test ( 'project viewers can view credentials' , async ( ) = > {
const teamProject = await createTeamProject ( ) ;
await linkUserToProject ( member , teamProject , 'project:viewer' ) ;
const savedCredential = await saveCredential ( randomCredentialPayload ( ) , {
project : teamProject ,
} ) ;
const response = await testServer
. authAgentFor ( member )
. get ( ` /credentials/ ${ savedCredential . id } ` ) ;
expect ( response . statusCode ) . toBe ( 200 ) ;
expect ( response . body . data ) . toMatchObject ( {
id : savedCredential.id ,
shared : [ { projectId : teamProject.id , role : 'credential:owner' } ] ,
homeProject : {
id : teamProject.id ,
} ,
sharedWithProjects : [ ] ,
scopes : [ 'credential:read' ] ,
} ) ;
expect ( response . body . data . data ) . toBeUndefined ( ) ;
} ) ;
2023-03-17 09:24:05 -07:00
test ( 'should retrieve owned cred for owner' , async ( ) = > {
const savedCredential = await saveCredential ( randomCredentialPayload ( ) , { user : owner } ) ;
const firstResponse = await authOwnerAgent . get ( ` /credentials/ ${ savedCredential . id } ` ) ;
expect ( firstResponse . statusCode ) . toBe ( 200 ) ;
2024-05-17 01:53:15 -07:00
const firstCredential : ListQuery.Credentials.WithOwnedByAndSharedWith = firstResponse . body . data ;
2023-03-17 09:24:05 -07:00
validateMainCredentialData ( firstCredential ) ;
expect ( firstCredential . data ) . toBeUndefined ( ) ;
2024-05-17 01:53:15 -07:00
expect ( firstCredential . homeProject ) . toMatchObject ( {
id : ownerPersonalProject.id ,
name : owner.createPersonalProjectName ( ) ,
type : ownerPersonalProject . type ,
2023-03-17 09:24:05 -07:00
} ) ;
2024-05-17 01:53:15 -07:00
expect ( firstCredential . sharedWithProjects ) . toHaveLength ( 0 ) ;
2022-09-21 01:20:29 -07:00
2023-03-17 09:24:05 -07:00
const secondResponse = await authOwnerAgent
. get ( ` /credentials/ ${ savedCredential . id } ` )
. query ( { includeData : true } ) ;
2022-09-21 01:20:29 -07:00
2023-03-17 09:24:05 -07:00
expect ( secondResponse . statusCode ) . toBe ( 200 ) ;
2022-09-21 01:20:29 -07:00
2023-03-17 09:24:05 -07:00
const { data : secondCredential } = secondResponse . body ;
validateMainCredentialData ( secondCredential ) ;
expect ( secondCredential . data ) . toBeDefined ( ) ;
2022-09-21 01:20:29 -07:00
} ) ;
2023-03-17 09:24:05 -07:00
test ( 'should retrieve non-owned cred for owner' , async ( ) = > {
2023-11-08 07:29:39 -08:00
const [ member1 , member2 ] = await createManyUsers ( 2 , {
2024-01-24 04:38:57 -08:00
role : 'global:member' ,
2023-03-17 09:24:05 -07:00
} ) ;
2024-05-17 01:53:15 -07:00
const member1PersonalProject = await projectRepository . getPersonalProjectForUserOrFail (
member1 . id ,
) ;
const member2PersonalProject = await projectRepository . getPersonalProjectForUserOrFail (
member2 . id ,
) ;
2022-09-21 01:20:29 -07:00
2023-03-17 09:24:05 -07:00
const savedCredential = await saveCredential ( randomCredentialPayload ( ) , { user : member1 } ) ;
2023-11-08 07:29:39 -08:00
await shareCredentialWithUsers ( savedCredential , [ member2 ] ) ;
2022-09-21 01:20:29 -07:00
2024-05-17 01:53:15 -07:00
const response1 = await authOwnerAgent . get ( ` /credentials/ ${ savedCredential . id } ` ) . expect ( 200 ) ;
const credential : ListQuery.Credentials.WithOwnedByAndSharedWith = response1 . body . data ;
validateMainCredentialData ( credential ) ;
expect ( credential . data ) . toBeUndefined ( ) ;
expect ( credential ) . toMatchObject ( {
homeProject : {
id : member1PersonalProject.id ,
name : member1.createPersonalProjectName ( ) ,
type : member1PersonalProject . type ,
} ,
sharedWithProjects : [
{
id : member2PersonalProject.id ,
name : member2.createPersonalProjectName ( ) ,
type : member2PersonalProject . type ,
} ,
] ,
2023-03-17 09:24:05 -07:00
} ) ;
2022-09-21 01:20:29 -07:00
2023-03-17 09:24:05 -07:00
const response2 = await authOwnerAgent
. get ( ` /credentials/ ${ savedCredential . id } ` )
2024-05-17 01:53:15 -07:00
. query ( { includeData : true } )
. expect ( 200 ) ;
2022-09-21 01:20:29 -07:00
2024-05-17 01:53:15 -07:00
const credential2 : ListQuery.Credentials.WithOwnedByAndSharedWith = response2 . body . data ;
2022-09-21 01:20:29 -07:00
2024-05-17 01:53:15 -07:00
validateMainCredentialData ( credential ) ;
expect ( credential2 . data ) . toBeDefined ( ) ; // Instance owners should be capable of editing all credentials
expect ( credential2 . sharedWithProjects ) . toHaveLength ( 1 ) ;
2022-09-21 01:20:29 -07:00
} ) ;
2023-03-17 09:24:05 -07:00
test ( 'should retrieve owned cred for member' , async ( ) = > {
2023-11-08 07:29:39 -08:00
const [ member1 , member2 , member3 ] = await createManyUsers ( 3 , {
2024-01-24 04:38:57 -08:00
role : 'global:member' ,
2023-03-17 09:24:05 -07:00
} ) ;
2024-05-17 01:53:15 -07:00
const member1PersonalProject = await projectRepository . getPersonalProjectForUserOrFail (
member1 . id ,
) ;
const member2PersonalProject = await projectRepository . getPersonalProjectForUserOrFail (
member2 . id ,
) ;
const member3PersonalProject = await projectRepository . getPersonalProjectForUserOrFail (
member3 . id ,
) ;
2023-07-13 01:14:48 -07:00
const authMemberAgent = testServer . authAgentFor ( member1 ) ;
2023-03-17 09:24:05 -07:00
const savedCredential = await saveCredential ( randomCredentialPayload ( ) , { user : member1 } ) ;
2023-11-08 07:29:39 -08:00
await shareCredentialWithUsers ( savedCredential , [ member2 , member3 ] ) ;
2023-03-17 09:24:05 -07:00
2024-05-17 01:53:15 -07:00
const firstResponse = await authMemberAgent
. get ( ` /credentials/ ${ savedCredential . id } ` )
. expect ( 200 ) ;
2023-03-17 09:24:05 -07:00
2024-05-17 01:53:15 -07:00
const firstCredential : ListQuery.Credentials.WithOwnedByAndSharedWith = firstResponse . body . data ;
2023-03-17 09:24:05 -07:00
validateMainCredentialData ( firstCredential ) ;
expect ( firstCredential . data ) . toBeUndefined ( ) ;
2024-05-17 01:53:15 -07:00
expect ( firstCredential ) . toMatchObject ( {
homeProject : {
id : member1PersonalProject.id ,
name : member1.createPersonalProjectName ( ) ,
type : 'personal' ,
} ,
sharedWithProjects : expect.arrayContaining ( [
{
id : member2PersonalProject.id ,
name : member2.createPersonalProjectName ( ) ,
type : member2PersonalProject . type ,
} ,
{
id : member3PersonalProject.id ,
name : member3.createPersonalProjectName ( ) ,
type : member3PersonalProject . type ,
} ,
] ) ,
2023-03-17 09:24:05 -07:00
} ) ;
2022-09-21 01:20:29 -07:00
2023-03-17 09:24:05 -07:00
const secondResponse = await authMemberAgent
. get ( ` /credentials/ ${ savedCredential . id } ` )
2024-05-17 01:53:15 -07:00
. query ( { includeData : true } )
. expect ( 200 ) ;
2022-09-21 01:20:29 -07:00
2024-05-17 01:53:15 -07:00
const secondCredential : ListQuery.Credentials.WithOwnedByAndSharedWith =
secondResponse . body . data ;
2023-03-17 09:24:05 -07:00
validateMainCredentialData ( secondCredential ) ;
expect ( secondCredential . data ) . toBeDefined ( ) ;
2024-05-17 01:53:15 -07:00
expect ( secondCredential . sharedWithProjects ) . toHaveLength ( 2 ) ;
2022-09-21 01:20:29 -07:00
} ) ;
2023-03-17 09:24:05 -07:00
test ( 'should not retrieve non-owned cred for member' , async ( ) = > {
const savedCredential = await saveCredential ( randomCredentialPayload ( ) , { user : owner } ) ;
2022-09-21 01:20:29 -07:00
2023-07-13 01:14:48 -07:00
const response = await testServer
. authAgentFor ( member )
. get ( ` /credentials/ ${ savedCredential . id } ` ) ;
2022-09-21 01:20:29 -07:00
2023-03-17 09:24:05 -07:00
expect ( response . statusCode ) . toBe ( 403 ) ;
expect ( response . body . data ) . toBeUndefined ( ) ; // owner's cred not returned
} ) ;
2022-09-21 01:20:29 -07:00
2023-03-17 09:24:05 -07:00
test ( 'should return 404 if cred not found' , async ( ) = > {
const response = await authOwnerAgent . get ( '/credentials/789' ) ;
expect ( response . statusCode ) . toBe ( 404 ) ;
2022-09-21 01:20:29 -07:00
2023-03-17 09:24:05 -07:00
const responseAbc = await authOwnerAgent . get ( '/credentials/abc' ) ;
expect ( responseAbc . statusCode ) . toBe ( 404 ) ;
2022-09-21 01:20:29 -07:00
2023-03-17 09:24:05 -07:00
// because EE router has precedence, check if forwards this route
const responseNew = await authOwnerAgent . get ( '/credentials/new' ) ;
expect ( responseNew . statusCode ) . toBe ( 200 ) ;
} ) ;
2022-09-21 01:20:29 -07:00
} ) ;
2024-06-06 02:55:48 -07:00
describe ( 'PATCH /credentials/:id' , ( ) = > {
test ( 'project viewer cannot update credentials' , async ( ) = > {
//
// ARRANGE
//
const teamProject = await createTeamProject ( '' , member ) ;
await linkUserToProject ( member , teamProject , 'project:viewer' ) ;
const savedCredential = await saveCredential ( randomCredentialPayload ( ) , {
project : teamProject ,
} ) ;
//
// ACT
//
const response = await testServer
. authAgentFor ( member )
. patch ( ` /credentials/ ${ savedCredential . id } ` )
. send ( { . . . randomCredentialPayload ( ) } ) ;
//
// ASSERT
//
expect ( response . statusCode ) . toBe ( 403 ) ;
expect ( response . body . message ) . toBe ( 'User is missing a scope required to perform this action' ) ;
} ) ;
} ) ;
2023-03-17 09:24:05 -07:00
// ----------------------------------------
// idempotent share/unshare
// ----------------------------------------
describe ( 'PUT /credentials/:id/share' , ( ) = > {
test ( 'should share the credential with the provided userIds and unshare it for missing ones' , async ( ) = > {
const savedCredential = await saveCredential ( randomCredentialPayload ( ) , { user : owner } ) ;
2022-09-21 01:20:29 -07:00
2023-11-08 07:29:39 -08:00
const [ member1 , member2 , member3 , member4 , member5 ] = await createManyUsers ( 5 , {
2024-01-24 04:38:57 -08:00
role : 'global:member' ,
2023-03-17 09:24:05 -07:00
} ) ;
2024-05-17 01:53:15 -07:00
// TODO: write helper for getting multiple personal projects by user id
const shareWithProjectIds = (
await Promise . all ( [
projectRepository . getPersonalProjectForUserOrFail ( member1 . id ) ,
projectRepository . getPersonalProjectForUserOrFail ( member2 . id ) ,
projectRepository . getPersonalProjectForUserOrFail ( member3 . id ) ,
] )
) . map ( ( project ) = > project . id ) ;
2022-09-21 01:20:29 -07:00
2023-11-08 07:29:39 -08:00
await shareCredentialWithUsers ( savedCredential , [ member4 , member5 ] ) ;
2022-09-21 01:20:29 -07:00
2023-03-17 09:24:05 -07:00
const response = await authOwnerAgent
. put ( ` /credentials/ ${ savedCredential . id } /share ` )
2024-05-17 01:53:15 -07:00
. send ( { shareWithIds : shareWithProjectIds } ) ;
2022-09-21 01:20:29 -07:00
2023-03-17 09:24:05 -07:00
expect ( response . statusCode ) . toBe ( 200 ) ;
expect ( response . body . data ) . toBeUndefined ( ) ;
2022-09-21 01:20:29 -07:00
2023-11-10 06:04:26 -08:00
const sharedCredentials = await Container . get ( SharedCredentialsRepository ) . find ( {
2023-03-17 09:24:05 -07:00
where : { credentialsId : savedCredential.id } ,
} ) ;
2022-09-21 01:20:29 -07:00
2023-03-17 09:24:05 -07:00
// check that sharings have been removed/added correctly
2024-05-17 01:53:15 -07:00
expect ( sharedCredentials . length ) . toBe ( shareWithProjectIds . length + 1 ) ; // +1 for the owner
2023-03-17 09:24:05 -07:00
sharedCredentials . forEach ( ( sharedCredential ) = > {
2024-05-17 01:53:15 -07:00
if ( sharedCredential . projectId === ownerPersonalProject . id ) {
2024-01-24 04:38:57 -08:00
expect ( sharedCredential . role ) . toBe ( 'credential:owner' ) ;
2023-03-17 09:24:05 -07:00
return ;
}
2024-05-17 01:53:15 -07:00
expect ( shareWithProjectIds ) . toContain ( sharedCredential . projectId ) ;
2024-01-24 04:38:57 -08:00
expect ( sharedCredential . role ) . toBe ( 'credential:user' ) ;
2023-03-17 09:24:05 -07:00
} ) ;
2024-01-23 03:03:59 -08:00
expect ( mailer . notifyCredentialsShared ) . toHaveBeenCalledTimes ( 1 ) ;
2024-05-17 01:53:15 -07:00
expect ( mailer . notifyCredentialsShared ) . toHaveBeenCalledWith (
expect . objectContaining ( {
newShareeIds : expect.arrayContaining ( [ member1 . id , member2 . id , member3 . id ] ) ,
sharer : expect.objectContaining ( { id : owner.id } ) ,
credentialsName : savedCredential.name ,
} ) ,
) ;
2022-09-21 01:20:29 -07:00
} ) ;
2023-03-17 09:24:05 -07:00
test ( 'should share the credential with the provided userIds' , async ( ) = > {
2023-11-08 07:29:39 -08:00
const [ member1 , member2 , member3 ] = await createManyUsers ( 3 , {
2024-01-24 04:38:57 -08:00
role : 'global:member' ,
2023-03-17 09:24:05 -07:00
} ) ;
2024-05-17 01:53:15 -07:00
const projectIds = (
await Promise . all ( [
projectRepository . getPersonalProjectForUserOrFail ( member1 . id ) ,
projectRepository . getPersonalProjectForUserOrFail ( member2 . id ) ,
projectRepository . getPersonalProjectForUserOrFail ( member3 . id ) ,
] )
) . map ( ( project ) = > project . id ) ;
// const memberIds = [member1.id, member2.id, member3.id];
2023-03-17 09:24:05 -07:00
const savedCredential = await saveCredential ( randomCredentialPayload ( ) , { user : owner } ) ;
2022-09-21 01:20:29 -07:00
2023-03-17 09:24:05 -07:00
const response = await authOwnerAgent
. put ( ` /credentials/ ${ savedCredential . id } /share ` )
2024-05-17 01:53:15 -07:00
. send ( { shareWithIds : projectIds } ) ;
2022-09-21 01:20:29 -07:00
2023-03-17 09:24:05 -07:00
expect ( response . statusCode ) . toBe ( 200 ) ;
expect ( response . body . data ) . toBeUndefined ( ) ;
2022-09-21 01:20:29 -07:00
2023-03-17 09:24:05 -07:00
// check that sharings got correctly set in DB
2023-11-10 06:04:26 -08:00
const sharedCredentials = await Container . get ( SharedCredentialsRepository ) . find ( {
2024-05-17 01:53:15 -07:00
where : { credentialsId : savedCredential.id , projectId : In ( projectIds ) } ,
2023-03-17 09:24:05 -07:00
} ) ;
2022-09-21 01:20:29 -07:00
2024-05-17 01:53:15 -07:00
expect ( sharedCredentials . length ) . toBe ( projectIds . length ) ;
2022-09-21 01:20:29 -07:00
2023-03-17 09:24:05 -07:00
sharedCredentials . forEach ( ( sharedCredential ) = > {
2024-01-24 04:38:57 -08:00
expect ( sharedCredential . role ) . toBe ( 'credential:user' ) ;
2023-03-17 09:24:05 -07:00
} ) ;
2022-09-21 01:20:29 -07:00
2023-03-17 09:24:05 -07:00
// check that owner still exists
2023-11-10 06:04:26 -08:00
const ownerSharedCredential = await Container . get ( SharedCredentialsRepository ) . findOneOrFail ( {
2024-05-17 01:53:15 -07:00
where : { credentialsId : savedCredential.id , projectId : ownerPersonalProject.id } ,
2023-03-17 09:24:05 -07:00
} ) ;
2022-09-21 01:20:29 -07:00
2024-01-24 04:38:57 -08:00
expect ( ownerSharedCredential . role ) . toBe ( 'credential:owner' ) ;
2024-01-23 03:03:59 -08:00
expect ( mailer . notifyCredentialsShared ) . toHaveBeenCalledTimes ( 1 ) ;
2022-09-21 01:20:29 -07:00
} ) ;
2023-03-17 09:24:05 -07:00
test ( 'should respond 403 for non-existing credentials' , async ( ) = > {
const response = await authOwnerAgent
2023-05-02 01:37:19 -07:00
. put ( '/credentials/1234567/share' )
2024-05-17 01:53:15 -07:00
. send ( { shareWithIds : [ memberPersonalProject . id ] } ) ;
2022-09-21 01:20:29 -07:00
2023-03-17 09:24:05 -07:00
expect ( response . statusCode ) . toBe ( 403 ) ;
2024-01-23 03:03:59 -08:00
expect ( mailer . notifyCredentialsShared ) . toHaveBeenCalledTimes ( 0 ) ;
2022-09-21 01:20:29 -07:00
} ) ;
2023-11-29 08:32:27 -08:00
test ( 'should respond 403 for non-owned credentials for shared members' , async ( ) = > {
2023-03-17 09:24:05 -07:00
const savedCredential = await saveCredential ( randomCredentialPayload ( ) , { user : member } ) ;
2022-09-21 01:20:29 -07:00
2023-11-29 08:32:27 -08:00
await shareCredentialWithUsers ( savedCredential , [ anotherMember ] ) ;
const response = await authAnotherMemberAgent
2023-03-17 09:24:05 -07:00
. put ( ` /credentials/ ${ savedCredential . id } /share ` )
2024-05-17 01:53:15 -07:00
. send ( { shareWithIds : [ ownerPersonalProject . id ] } ) ;
2022-09-21 01:20:29 -07:00
2023-03-17 09:24:05 -07:00
expect ( response . statusCode ) . toBe ( 403 ) ;
2023-11-29 08:32:27 -08:00
const sharedCredentials = await Container . get ( SharedCredentialsRepository ) . find ( {
where : { credentialsId : savedCredential.id } ,
} ) ;
expect ( sharedCredentials ) . toHaveLength ( 2 ) ;
2024-01-23 03:03:59 -08:00
expect ( mailer . notifyCredentialsShared ) . toHaveBeenCalledTimes ( 0 ) ;
2023-11-29 08:32:27 -08:00
} ) ;
test ( 'should respond 403 for non-owned credentials for non-shared members sharing with self' , async ( ) = > {
const savedCredential = await saveCredential ( randomCredentialPayload ( ) , { user : member } ) ;
const response = await authAnotherMemberAgent
. put ( ` /credentials/ ${ savedCredential . id } /share ` )
2024-05-17 01:53:15 -07:00
. send ( { shareWithIds : [ anotherMemberPersonalProject . id ] } ) ;
2023-11-29 08:32:27 -08:00
expect ( response . statusCode ) . toBe ( 403 ) ;
const sharedCredentials = await Container . get ( SharedCredentialsRepository ) . find ( {
where : { credentialsId : savedCredential.id } ,
} ) ;
expect ( sharedCredentials ) . toHaveLength ( 1 ) ;
2024-01-23 03:03:59 -08:00
expect ( mailer . notifyCredentialsShared ) . toHaveBeenCalledTimes ( 0 ) ;
2023-11-29 08:32:27 -08:00
} ) ;
test ( 'should respond 403 for non-owned credentials for non-shared members sharing' , async ( ) = > {
const savedCredential = await saveCredential ( randomCredentialPayload ( ) , { user : member } ) ;
2024-01-24 04:38:57 -08:00
const tempUser = await createUser ( { role : 'global:member' } ) ;
2024-05-17 01:53:15 -07:00
const tempUserPersonalProject = await projectRepository . getPersonalProjectForUserOrFail (
tempUser . id ,
) ;
2023-11-29 08:32:27 -08:00
const response = await authAnotherMemberAgent
. put ( ` /credentials/ ${ savedCredential . id } /share ` )
2024-05-17 01:53:15 -07:00
. send ( { shareWithIds : [ tempUserPersonalProject . id ] } ) ;
2023-11-29 08:32:27 -08:00
expect ( response . statusCode ) . toBe ( 403 ) ;
const sharedCredentials = await Container . get ( SharedCredentialsRepository ) . find ( {
where : { credentialsId : savedCredential.id } ,
} ) ;
expect ( sharedCredentials ) . toHaveLength ( 1 ) ;
2024-01-23 03:03:59 -08:00
expect ( mailer . notifyCredentialsShared ) . toHaveBeenCalledTimes ( 0 ) ;
2023-11-29 08:32:27 -08:00
} ) ;
test ( 'should respond 200 for non-owned credentials for owners' , async ( ) = > {
const savedCredential = await saveCredential ( randomCredentialPayload ( ) , { user : member } ) ;
2024-05-31 05:06:13 -07:00
await authOwnerAgent
2023-11-29 08:32:27 -08:00
. put ( ` /credentials/ ${ savedCredential . id } /share ` )
2024-05-17 01:53:15 -07:00
. send ( { shareWithIds : [ anotherMemberPersonalProject . id ] } )
. expect ( 200 ) ;
2023-11-29 08:32:27 -08:00
const sharedCredentials = await Container . get ( SharedCredentialsRepository ) . find ( {
where : { credentialsId : savedCredential.id } ,
} ) ;
expect ( sharedCredentials ) . toHaveLength ( 2 ) ;
2024-01-23 03:03:59 -08:00
expect ( mailer . notifyCredentialsShared ) . toHaveBeenCalledTimes ( 1 ) ;
2022-09-21 01:20:29 -07:00
} ) ;
2024-05-17 01:53:15 -07:00
test ( 'should not ignore pending sharee' , async ( ) = > {
2024-01-24 04:38:57 -08:00
const memberShell = await createUserShell ( 'global:member' ) ;
2024-05-17 01:53:15 -07:00
const memberShellPersonalProject = await projectRepository . getPersonalProjectForUserOrFail (
memberShell . id ,
) ;
2023-03-17 09:24:05 -07:00
const savedCredential = await saveCredential ( randomCredentialPayload ( ) , { user : owner } ) ;
2022-09-21 01:20:29 -07:00
2024-05-17 01:53:15 -07:00
await authOwnerAgent
2023-03-17 09:24:05 -07:00
. put ( ` /credentials/ ${ savedCredential . id } /share ` )
2024-05-17 01:53:15 -07:00
. send ( { shareWithIds : [ memberShellPersonalProject . id ] } )
. expect ( 200 ) ;
2022-09-21 01:20:29 -07:00
2023-11-10 06:04:26 -08:00
const sharedCredentials = await Container . get ( SharedCredentialsRepository ) . find ( {
2023-03-17 09:24:05 -07:00
where : { credentialsId : savedCredential.id } ,
} ) ;
2022-09-21 01:20:29 -07:00
2024-05-17 01:53:15 -07:00
expect ( sharedCredentials ) . toHaveLength ( 2 ) ;
expect (
sharedCredentials . find ( ( c ) = > c . projectId === ownerPersonalProject . id ) ,
) . not . toBeUndefined ( ) ;
expect (
sharedCredentials . find ( ( c ) = > c . projectId === memberShellPersonalProject . id ) ,
) . not . toBeUndefined ( ) ;
2023-03-17 09:24:05 -07:00
} ) ;
2022-09-21 01:20:29 -07:00
2023-03-17 09:24:05 -07:00
test ( 'should ignore non-existing sharee' , async ( ) = > {
const savedCredential = await saveCredential ( randomCredentialPayload ( ) , { user : owner } ) ;
2022-09-21 01:20:29 -07:00
2023-03-17 09:24:05 -07:00
const response = await authOwnerAgent
. put ( ` /credentials/ ${ savedCredential . id } /share ` )
. send ( { shareWithIds : [ 'bce38a11-5e45-4d1c-a9ee-36e4a20ab0fc' ] } ) ;
2022-09-21 01:20:29 -07:00
2023-03-17 09:24:05 -07:00
expect ( response . statusCode ) . toBe ( 200 ) ;
2022-09-21 01:20:29 -07:00
2023-11-10 06:04:26 -08:00
const sharedCredentials = await Container . get ( SharedCredentialsRepository ) . find ( {
2023-03-17 09:24:05 -07:00
where : { credentialsId : savedCredential.id } ,
} ) ;
2022-09-21 01:20:29 -07:00
2023-03-17 09:24:05 -07:00
expect ( sharedCredentials ) . toHaveLength ( 1 ) ;
2024-05-17 01:53:15 -07:00
expect ( sharedCredentials [ 0 ] . projectId ) . toBe ( ownerPersonalProject . id ) ;
2024-01-29 07:15:30 -08:00
expect ( mailer . notifyCredentialsShared ) . toHaveBeenCalledTimes ( 1 ) ;
2022-09-21 01:20:29 -07:00
} ) ;
2023-03-17 09:24:05 -07:00
test ( 'should respond 400 if invalid payload is provided' , async ( ) = > {
const savedCredential = await saveCredential ( randomCredentialPayload ( ) , { user : owner } ) ;
2022-09-21 01:20:29 -07:00
2023-03-17 09:24:05 -07:00
const responses = await Promise . all ( [
authOwnerAgent . put ( ` /credentials/ ${ savedCredential . id } /share ` ) . send ( ) ,
authOwnerAgent . put ( ` /credentials/ ${ savedCredential . id } /share ` ) . send ( { shareWithIds : [ 1 ] } ) ,
] ) ;
2022-09-21 01:20:29 -07:00
2023-03-17 09:24:05 -07:00
responses . forEach ( ( response ) = > expect ( response . statusCode ) . toBe ( 400 ) ) ;
2024-01-23 03:03:59 -08:00
expect ( mailer . notifyCredentialsShared ) . toHaveBeenCalledTimes ( 0 ) ;
2022-09-21 01:20:29 -07:00
} ) ;
2024-01-31 00:48:48 -08:00
2023-03-17 09:24:05 -07:00
test ( 'should unshare the credential' , async ( ) = > {
const savedCredential = await saveCredential ( randomCredentialPayload ( ) , { user : owner } ) ;
2022-09-21 01:20:29 -07:00
2023-11-08 07:29:39 -08:00
const [ member1 , member2 ] = await createManyUsers ( 2 , {
2024-01-24 04:38:57 -08:00
role : 'global:member' ,
2023-03-17 09:24:05 -07:00
} ) ;
2022-09-21 01:20:29 -07:00
2023-11-08 07:29:39 -08:00
await shareCredentialWithUsers ( savedCredential , [ member1 , member2 ] ) ;
2022-09-21 01:20:29 -07:00
2023-03-17 09:24:05 -07:00
const response = await authOwnerAgent
2022-09-21 01:20:29 -07:00
. put ( ` /credentials/ ${ savedCredential . id } /share ` )
2023-03-17 09:24:05 -07:00
. send ( { shareWithIds : [ ] } ) ;
2022-09-21 01:20:29 -07:00
2023-03-17 09:24:05 -07:00
expect ( response . statusCode ) . toBe ( 200 ) ;
2022-09-21 01:20:29 -07:00
2023-11-10 06:04:26 -08:00
const sharedCredentials = await Container . get ( SharedCredentialsRepository ) . find ( {
2023-03-17 09:24:05 -07:00
where : { credentialsId : savedCredential.id } ,
} ) ;
2022-09-21 01:20:29 -07:00
2023-03-17 09:24:05 -07:00
expect ( sharedCredentials ) . toHaveLength ( 1 ) ;
2024-05-17 01:53:15 -07:00
expect ( sharedCredentials [ 0 ] . projectId ) . toBe ( ownerPersonalProject . id ) ;
2024-01-29 07:15:30 -08:00
expect ( mailer . notifyCredentialsShared ) . toHaveBeenCalledTimes ( 1 ) ;
} ) ;
test ( 'should not call internal hooks listener for email sent if emailing is disabled' , async ( ) = > {
config . set ( 'userManagement.emails.mode' , '' ) ;
const savedCredential = await saveCredential ( randomCredentialPayload ( ) , { user : owner } ) ;
const [ member1 , member2 ] = await createManyUsers ( 2 , {
role : 'global:member' ,
} ) ;
await shareCredentialWithUsers ( savedCredential , [ member1 , member2 ] ) ;
const response = await authOwnerAgent
. put ( ` /credentials/ ${ savedCredential . id } /share ` )
. send ( { shareWithIds : [ ] } ) ;
expect ( response . statusCode ) . toBe ( 200 ) ;
config . set ( 'userManagement.emails.mode' , 'smtp' ) ;
2022-09-21 01:20:29 -07:00
} ) ;
2024-08-09 03:59:28 -07:00
test ( 'member should be able to share from personal project to team project that member has access to' , async ( ) = > {
const savedCredential = await saveCredential ( randomCredentialPayload ( ) , { user : member } ) ;
const testProject = await createTeamProject ( ) ;
await linkUserToProject ( member , testProject , 'project:editor' ) ;
const response = await authMemberAgent
. put ( ` /credentials/ ${ savedCredential . id } /share ` )
. send ( { shareWithIds : [ testProject . id ] } ) ;
expect ( response . statusCode ) . toBe ( 200 ) ;
expect ( response . body . data ) . toBeUndefined ( ) ;
const shares = await getCredentialSharings ( savedCredential ) ;
const testShare = shares . find ( ( s ) = > s . projectId === testProject . id ) ;
expect ( testShare ) . not . toBeUndefined ( ) ;
expect ( testShare ? . role ) . toBe ( 'credential:user' ) ;
} ) ;
test ( 'member should be able to share from team project to personal project' , async ( ) = > {
const testProject = await createTeamProject ( undefined , member ) ;
const savedCredential = await saveCredential ( randomCredentialPayload ( ) , {
project : testProject ,
} ) ;
const response = await authMemberAgent
. put ( ` /credentials/ ${ savedCredential . id } /share ` )
. send ( { shareWithIds : [ anotherMemberPersonalProject . id ] } ) ;
expect ( response . statusCode ) . toBe ( 200 ) ;
expect ( response . body . data ) . toBeUndefined ( ) ;
const shares = await getCredentialSharings ( savedCredential ) ;
const testShare = shares . find ( ( s ) = > s . projectId === anotherMemberPersonalProject . id ) ;
expect ( testShare ) . not . toBeUndefined ( ) ;
expect ( testShare ? . role ) . toBe ( 'credential:user' ) ;
} ) ;
test ( 'member should be able to share from team project to team project that member has access to' , async ( ) = > {
const testProject = await createTeamProject ( undefined , member ) ;
const testProject2 = await createTeamProject ( ) ;
await linkUserToProject ( member , testProject2 , 'project:editor' ) ;
const savedCredential = await saveCredential ( randomCredentialPayload ( ) , {
project : testProject ,
} ) ;
const response = await authMemberAgent
. put ( ` /credentials/ ${ savedCredential . id } /share ` )
. send ( { shareWithIds : [ testProject2 . id ] } ) ;
expect ( response . statusCode ) . toBe ( 200 ) ;
expect ( response . body . data ) . toBeUndefined ( ) ;
const shares = await getCredentialSharings ( savedCredential ) ;
const testShare = shares . find ( ( s ) = > s . projectId === testProject2 . id ) ;
expect ( testShare ) . not . toBeUndefined ( ) ;
expect ( testShare ? . role ) . toBe ( 'credential:user' ) ;
} ) ;
test ( 'admins should be able to share from any team project to any team project ' , async ( ) = > {
const testProject = await createTeamProject ( ) ;
const testProject2 = await createTeamProject ( ) ;
const savedCredential = await saveCredential ( randomCredentialPayload ( ) , {
project : testProject ,
} ) ;
const response = await authOwnerAgent
. put ( ` /credentials/ ${ savedCredential . id } /share ` )
. send ( { shareWithIds : [ testProject2 . id ] } ) ;
expect ( response . statusCode ) . toBe ( 200 ) ;
expect ( response . body . data ) . toBeUndefined ( ) ;
const shares = await getCredentialSharings ( savedCredential ) ;
const testShare = shares . find ( ( s ) = > s . projectId === testProject2 . id ) ;
expect ( testShare ) . not . toBeUndefined ( ) ;
expect ( testShare ? . role ) . toBe ( 'credential:user' ) ;
} ) ;
test ( "admins should be able to share from any team project to any user's personal project " , async ( ) = > {
const testProject = await createTeamProject ( ) ;
const savedCredential = await saveCredential ( randomCredentialPayload ( ) , {
project : testProject ,
} ) ;
const response = await authOwnerAgent
. put ( ` /credentials/ ${ savedCredential . id } /share ` )
. send ( { shareWithIds : [ memberPersonalProject . id ] } ) ;
expect ( response . statusCode ) . toBe ( 200 ) ;
expect ( response . body . data ) . toBeUndefined ( ) ;
const shares = await getCredentialSharings ( savedCredential ) ;
const testShare = shares . find ( ( s ) = > s . projectId === memberPersonalProject . id ) ;
expect ( testShare ) . not . toBeUndefined ( ) ;
expect ( testShare ? . role ) . toBe ( 'credential:user' ) ;
} ) ;
test ( 'admins should be able to share from any personal project to any team project ' , async ( ) = > {
const testProject = await createTeamProject ( ) ;
const savedCredential = await saveCredential ( randomCredentialPayload ( ) , {
user : member ,
} ) ;
const response = await authOwnerAgent
. put ( ` /credentials/ ${ savedCredential . id } /share ` )
. send ( { shareWithIds : [ testProject . id ] } ) ;
expect ( response . statusCode ) . toBe ( 200 ) ;
expect ( response . body . data ) . toBeUndefined ( ) ;
const shares = await getCredentialSharings ( savedCredential ) ;
const testShare = shares . find ( ( s ) = > s . projectId === testProject . id ) ;
expect ( testShare ) . not . toBeUndefined ( ) ;
expect ( testShare ? . role ) . toBe ( 'credential:user' ) ;
} ) ;
2022-09-21 01:20:29 -07:00
} ) ;
2024-06-04 04:54:48 -07:00
describe ( 'PUT /:credentialId/transfer' , ( ) = > {
test ( 'cannot transfer into the same project' , async ( ) = > {
const destinationProject = await createTeamProject ( 'Destination Project' , member ) ;
const credential = await saveCredential ( randomCredentialPayload ( ) , {
project : destinationProject ,
} ) ;
await testServer
. authAgentFor ( member )
. put ( ` /credentials/ ${ credential . id } /transfer ` )
. send ( { destinationProjectId : destinationProject.id } )
. expect ( 400 ) ;
} ) ;
test ( 'cannot transfer into a personal project' , async ( ) = > {
const credential = await saveCredential ( randomCredentialPayload ( ) , {
user : member ,
} ) ;
await testServer
. authAgentFor ( member )
. put ( ` /credentials/ ${ credential . id } /transfer ` )
. send ( { destinationProjectId : memberPersonalProject.id } )
. expect ( 400 ) ;
} ) ;
test ( 'cannot transfer somebody elses credential' , async ( ) = > {
const destinationProject = await createTeamProject ( 'Destination Project' , member ) ;
const credential = await saveCredential ( randomCredentialPayload ( ) , {
user : anotherMember ,
} ) ;
await testServer
. authAgentFor ( member )
. put ( ` /credentials/ ${ credential . id } /transfer ` )
. send ( { destinationProjectId : destinationProject.id } )
. expect ( 403 ) ;
} ) ;
test ( "cannot transfer if you're not a member of the destination project" , async ( ) = > {
const credential = await saveCredential ( randomCredentialPayload ( ) , {
user : member ,
} ) ;
const destinationProject = await createTeamProject ( 'Team Project' ) ;
await testServer
. authAgentFor ( member )
. put ( ` /credentials/ ${ credential . id } /transfer ` )
. send ( { destinationProjectId : destinationProject.id } )
. expect ( 404 ) ;
} ) ;
test ( 'project:editors cannot transfer credentials' , async ( ) = > {
//
// ARRANGE
//
const sourceProject = await createTeamProject ( 'Source Project' ) ;
await linkUserToProject ( member , sourceProject , 'project:editor' ) ;
const credential = await saveCredential ( randomCredentialPayload ( ) , {
project : sourceProject ,
} ) ;
const destinationProject = await createTeamProject ( 'Destination Project' , member ) ;
//
// ACT & ASSERT
//
await testServer
. authAgentFor ( member )
. put ( ` /credentials/ ${ credential . id } /transfer ` )
. send ( { destinationProjectId : destinationProject.id } )
. expect ( 403 ) ;
} ) ;
test ( 'transferring from a personal project to a team project severs all sharings' , async ( ) = > {
//
// ARRANGE
//
const credential = await saveCredential ( randomCredentialPayload ( ) , { user : member } ) ;
// these sharings should be deleted by the transfer
await shareCredentialWithUsers ( credential , [ anotherMember , owner ] ) ;
const destinationProject = await createTeamProject ( 'Destination Project' , member ) ;
//
// ACT
//
const response = await testServer
. authAgentFor ( member )
. put ( ` /credentials/ ${ credential . id } /transfer ` )
. send ( { destinationProjectId : destinationProject.id } )
. expect ( 200 ) ;
//
// ASSERT
//
expect ( response . body ) . toEqual ( { } ) ;
const allSharings = await getCredentialSharings ( credential ) ;
expect ( allSharings ) . toHaveLength ( 1 ) ;
expect ( allSharings [ 0 ] ) . toMatchObject ( {
projectId : destinationProject.id ,
credentialsId : credential.id ,
role : 'credential:owner' ,
} ) ;
} ) ;
test ( 'can transfer from team to another team project' , async ( ) = > {
//
// ARRANGE
//
const sourceProject = await createTeamProject ( 'Team Project 1' , member ) ;
const credential = await saveCredential ( randomCredentialPayload ( ) , {
project : sourceProject ,
} ) ;
const destinationProject = await createTeamProject ( 'Team Project 2' , member ) ;
//
// ACT
//
const response = await testServer
. authAgentFor ( member )
. put ( ` /credentials/ ${ credential . id } /transfer ` )
. send ( { destinationProjectId : destinationProject.id } )
. expect ( 200 ) ;
//
// ASSERT
//
expect ( response . body ) . toEqual ( { } ) ;
const allSharings = await getCredentialSharings ( credential ) ;
expect ( allSharings ) . toHaveLength ( 1 ) ;
expect ( allSharings [ 0 ] ) . toMatchObject ( {
projectId : destinationProject.id ,
credentialsId : credential.id ,
role : 'credential:owner' ,
} ) ;
} ) ;
test . each ( [
[ 'owners' , ( ) = > owner ] ,
[ 'admins' , ( ) = > admin ] ,
] ) (
'%s can always transfer from any personal or team project into any team project' ,
async ( _name , actor ) = > {
//
// ARRANGE
//
const sourceProject = await createTeamProject ( 'Source Project' , member ) ;
const teamCredential = await saveCredential ( randomCredentialPayload ( ) , {
project : sourceProject ,
} ) ;
const personalCredential = await saveCredential ( randomCredentialPayload ( ) , { user : member } ) ;
const destinationProject = await createTeamProject ( 'Destination Project' , member ) ;
//
// ACT
//
const response1 = await testServer
. authAgentFor ( actor ( ) )
. put ( ` /credentials/ ${ teamCredential . id } /transfer ` )
. send ( { destinationProjectId : destinationProject.id } )
. expect ( 200 ) ;
const response2 = await testServer
. authAgentFor ( actor ( ) )
. put ( ` /credentials/ ${ personalCredential . id } /transfer ` )
. send ( { destinationProjectId : destinationProject.id } )
. expect ( 200 ) ;
//
// ASSERT
//
expect ( response1 . body ) . toEqual ( { } ) ;
expect ( response2 . body ) . toEqual ( { } ) ;
{
const allSharings = await getCredentialSharings ( teamCredential ) ;
expect ( allSharings ) . toHaveLength ( 1 ) ;
expect ( allSharings [ 0 ] ) . toMatchObject ( {
projectId : destinationProject.id ,
credentialsId : teamCredential.id ,
role : 'credential:owner' ,
} ) ;
}
{
const allSharings = await getCredentialSharings ( personalCredential ) ;
expect ( allSharings ) . toHaveLength ( 1 ) ;
expect ( allSharings [ 0 ] ) . toMatchObject ( {
projectId : destinationProject.id ,
credentialsId : personalCredential.id ,
role : 'credential:owner' ,
} ) ;
}
} ,
) ;
test . each ( [
[ 'owners' , ( ) = > owner ] ,
[ 'admins' , ( ) = > admin ] ,
] ) ( '%s cannot transfer into personal projects' , async ( _name , actor ) = > {
//
// ARRANGE
//
const sourceProject = await createTeamProject ( 'Source Project' , member ) ;
const teamCredential = await saveCredential ( randomCredentialPayload ( ) , {
project : sourceProject ,
} ) ;
const personalCredential = await saveCredential ( randomCredentialPayload ( ) , { user : member } ) ;
const destinationProject = anotherMemberPersonalProject ;
//
// ACT & ASSERT
//
await testServer
. authAgentFor ( actor ( ) )
. put ( ` /credentials/ ${ teamCredential . id } /transfer ` )
. send ( { destinationProjectId : destinationProject.id } )
. expect ( 400 ) ;
await testServer
. authAgentFor ( actor ( ) )
. put ( ` /credentials/ ${ personalCredential . id } /transfer ` )
. send ( { destinationProjectId : destinationProject.id } )
. expect ( 400 ) ;
} ) ;
} ) ;
2023-12-07 02:35:40 -08:00
function validateMainCredentialData ( credential : ListQuery.Credentials.WithOwnedByAndSharedWith ) {
2022-09-21 01:20:29 -07:00
expect ( typeof credential . name ) . toBe ( 'string' ) ;
expect ( typeof credential . type ) . toBe ( 'string' ) ;
2024-05-17 01:53:15 -07:00
expect ( credential . homeProject ) . toBeDefined ( ) ;
expect ( Array . isArray ( credential . sharedWithProjects ) ) . toBe ( true ) ;
2022-09-21 01:20:29 -07:00
}