-
Notifications
You must be signed in to change notification settings - Fork 371
chore(repo,backend): Add machine-to-machine integration tests #6500
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 19 commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
a9787ef
chore(repo): Improve express e2e tests
wobsoriano dc8c7be
chore: placeholder changeset
wobsoriano 58191b1
chore(express): Deprecate enableHandshake option
wobsoriano 1707c12
chore: add changeset
wobsoriano 56867b2
add machine secret key to custom authenticate request
wobsoriano 757ea76
add jsdoc
wobsoriano dc77a3f
fix changeset
wobsoriano ebff149
update jsdoc
wobsoriano 4f0bd61
add back deprecated option but keep deprecated status
wobsoriano ebd8b96
update changeset
wobsoriano 398c3cf
init m2m e2e
wobsoriano 7fad6ca
chore(repo): Add machine-to-machine e2e tests
wobsoriano 48bc893
improvements
wobsoriano 2fa0b44
clean up long running apps
wobsoriano 98b79d7
add turbo entry for machine tests
wobsoriano 91a7e56
revert package versions
wobsoriano c224d6f
Merge branch 'main' into rob/m2m-e2e
wobsoriano b52c3b9
Merge branch 'main' into rob/m2m-e2e
wobsoriano 45a490d
ci: add machine to tests
wobsoriano 268c92b
simplify test
wobsoriano 8807419
fix tests
wobsoriano 2dff52b
add jsdoc description for secondsUntilExpiration
wobsoriano b628254
chore: add changeset
wobsoriano 53a9eb8
Apply suggestion from @panteliselef
wobsoriano File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
import { createClerkClient, type M2MToken, type Machine } from '@clerk/backend'; | ||
import { faker } from '@faker-js/faker'; | ||
import { expect, test } from '@playwright/test'; | ||
|
||
import type { Application } from '../../models/application'; | ||
import { appConfigs } from '../../presets'; | ||
import { instanceKeys } from '../../presets/envs'; | ||
import { createTestUtils } from '../../testUtils'; | ||
|
||
test.describe('machine-to-machine auth @machine', () => { | ||
test.describe.configure({ mode: 'parallel' }); | ||
let app: Application; | ||
let primaryApiServer: Machine; | ||
let emailServer: Machine; | ||
let analyticsServer: Machine; | ||
let emailServerM2MToken: M2MToken; | ||
let analyticsServerM2MToken: M2MToken; | ||
|
||
test.beforeAll(async () => { | ||
const fakeCompanyName = faker.company.name(); | ||
|
||
// Create primary machine using instance secret key | ||
const client = createClerkClient({ | ||
secretKey: instanceKeys.get('with-api-keys').sk, | ||
}); | ||
primaryApiServer = await client.machines.create({ | ||
name: `${fakeCompanyName} Primary API Server`, | ||
}); | ||
|
||
app = await appConfigs.express.vite | ||
.clone() | ||
.addFile( | ||
'src/server/main.ts', | ||
() => ` | ||
import 'dotenv/config'; | ||
import { clerkMiddleware, getAuth } from '@clerk/express'; | ||
import express from 'express'; | ||
import ViteExpress from 'vite-express'; | ||
|
||
const app = express(); | ||
|
||
app.use( | ||
clerkMiddleware({ | ||
publishableKey: process.env.VITE_CLERK_PUBLISHABLE_KEY, | ||
machineSecretKey: process.env.CLERK_MACHINE_SECRET_KEY, | ||
}), | ||
); | ||
|
||
app.get('/api/protected', (req, res) => { | ||
const { machineId } = getAuth(req, { acceptsToken: 'm2m_token' }); | ||
if (!machineId) { | ||
res.status(401).send('Unauthorized'); | ||
return; | ||
} | ||
|
||
res.send('Protected response'); | ||
}); | ||
|
||
ViteExpress.listen(app, process.env.PORT, () => console.log('Server started')); | ||
`, | ||
) | ||
.commit(); | ||
|
||
await app.setup(); | ||
|
||
// Using the created machine, set a machine secret key using the primary machine's secret key | ||
const env = appConfigs.envs.withAPIKeys | ||
.clone() | ||
.setEnvVariable('private', 'CLERK_MACHINE_SECRET_KEY', primaryApiServer.secretKey); | ||
await app.withEnv(env); | ||
await app.dev(); | ||
|
||
const u = createTestUtils({ app }); | ||
|
||
// Email server can access primary API server | ||
emailServer = await u.services.clerk.machines.create({ | ||
name: `${fakeCompanyName} Email Server`, | ||
scopedMachines: [primaryApiServer.id], | ||
}); | ||
emailServerM2MToken = await u.services.clerk.m2mTokens.create({ | ||
machineSecretKey: emailServer.secretKey, | ||
secondsUntilExpiration: 60 * 30, | ||
}); | ||
|
||
// Analytics server cannot access primary API server | ||
analyticsServer = await u.services.clerk.machines.create({ | ||
name: `${fakeCompanyName} Analytics Server`, | ||
// No scoped machines | ||
}); | ||
analyticsServerM2MToken = await u.services.clerk.m2mTokens.create({ | ||
machineSecretKey: analyticsServer.secretKey, | ||
secondsUntilExpiration: 60 * 30, | ||
}); | ||
}); | ||
|
||
test.afterAll(async () => { | ||
const u = createTestUtils({ app }); | ||
|
||
await u.services.clerk.m2mTokens.revoke({ | ||
m2mTokenId: emailServerM2MToken.id, | ||
}); | ||
await u.services.clerk.m2mTokens.revoke({ | ||
m2mTokenId: analyticsServerM2MToken.id, | ||
}); | ||
await u.services.clerk.machines.delete(emailServer.id); | ||
await u.services.clerk.machines.delete(primaryApiServer.id); | ||
await u.services.clerk.machines.delete(analyticsServer.id); | ||
|
||
await app.teardown(); | ||
}); | ||
|
||
test('rejects requests with invalid M2M tokens', async ({ page, context }) => { | ||
const u = createTestUtils({ app, page, context }); | ||
|
||
const res = await u.page.request.get(app.serverUrl + '/api/protected', { | ||
headers: { | ||
Authorization: `Bearer invalid`, | ||
}, | ||
}); | ||
expect(res.status()).toBe(401); | ||
expect(await res.text()).toBe('Unauthorized'); | ||
|
||
const res2 = await u.page.request.get(app.serverUrl + '/api/protected', { | ||
headers: { | ||
Authorization: `Bearer mt_xxx`, | ||
}, | ||
}); | ||
expect(res2.status()).toBe(401); | ||
expect(await res2.text()).toBe('Unauthorized'); | ||
}); | ||
|
||
test('rejects M2M requests when sender machine lacks access to receiver machine', async ({ page, context }) => { | ||
const u = createTestUtils({ app, page, context }); | ||
|
||
const res = await u.page.request.get(app.serverUrl + '/api/protected', { | ||
headers: { | ||
Authorization: `Bearer ${analyticsServerM2MToken.secret}`, | ||
}, | ||
}); | ||
expect(res.status()).toBe(401); | ||
expect(await res.text()).toBe('Unauthorized'); | ||
}); | ||
|
||
test('authorizes M2M requests when sender machine has proper access to receiver machine', async ({ | ||
page, | ||
context, | ||
}) => { | ||
const u = createTestUtils({ app, page, context }); | ||
|
||
// Email server can access primary API server | ||
const res = await u.page.request.get(app.serverUrl + '/api/protected', { | ||
headers: { | ||
Authorization: `Bearer ${emailServerM2MToken.secret}`, | ||
}, | ||
}); | ||
expect(res.status()).toBe(200); | ||
expect(await res.text()).toBe('Protected response'); | ||
|
||
// Analytics server can access primary API server after adding scope | ||
await u.services.clerk.machines.createScope(analyticsServer.id, primaryApiServer.id); | ||
const m2mToken = await u.services.clerk.m2mTokens.create({ | ||
machineSecretKey: analyticsServer.secretKey, | ||
secondsUntilExpiration: 60 * 30, | ||
}); | ||
|
||
const res2 = await u.page.request.get(app.serverUrl + '/api/protected', { | ||
headers: { | ||
Authorization: `Bearer ${m2mToken.secret}`, | ||
}, | ||
}); | ||
expect(res2.status()).toBe(200); | ||
expect(await res2.text()).toBe('Protected response'); | ||
await u.services.clerk.m2mTokens.revoke({ | ||
m2mTokenId: m2mToken.id, | ||
}); | ||
}); | ||
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -344,6 +344,18 @@ | |
"env": ["CLEANUP", "DEBUG", "E2E_*", "INTEGRATION_INSTANCE_KEYS"], | ||
"inputs": ["integration/**"], | ||
"outputLogs": "new-only" | ||
}, | ||
"//#test:integration:machine": { | ||
"dependsOn": [ | ||
"@clerk/testing#build", | ||
"@clerk/clerk-js#build", | ||
"@clerk/backend#build", | ||
"@clerk/nextjs#build", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We are testing against nextjs, right ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Related to my comment above, the api keys test needs nextjs |
||
"@clerk/express#build" | ||
], | ||
"env": ["CLEANUP", "DEBUG", "E2E_*", "INTEGRATION_INSTANCE_KEYS"], | ||
"inputs": ["integration/**"], | ||
"outputLogs": "new-only" | ||
} | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🙃
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So there's a follow up PR for this that will group all "machine" related tests (api keys, m2m)