-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(auth): add EntraId integration tests
- Add integration tests for token renewal and re-authentication flows - Update credentials provider to use uniqueId as username instead of account username - Add test utilities for loading Redis endpoint configurations - Split TypeScript configs into separate files for samples and integration tests
- Loading branch information
1 parent
ac972bd
commit 2d82e6b
Showing
9 changed files
with
199 additions
and
12 deletions.
There are no files selected for viewing
134 changes: 134 additions & 0 deletions
134
packages/entraid/integration-tests/entraid-integration.spec.ts
This file contains 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,134 @@ | ||
import { BasicAuth } from '@redis/authx'; | ||
import { createClient } from '@redis/client'; | ||
import { EntraIdCredentialsProviderFactory } from '../lib/entra-id-credentials-provider-factory'; | ||
import { strict as assert } from 'node:assert'; | ||
import { spy, SinonSpy } from 'sinon'; | ||
import { randomUUID } from 'crypto'; | ||
import { loadFromJson, RedisEndpointsConfig } from '@redis/test-utils/lib/cae-client-testing' | ||
|
||
describe('EntraID Integration Tests', () => { | ||
|
||
interface TestConfig { | ||
clientId: string; | ||
clientSecret: string; | ||
authority: string; | ||
tenantId: string; | ||
redisScopes: string; | ||
cert: string; | ||
privateKey: string; | ||
userAssignedManagedId: string | ||
endpoints: RedisEndpointsConfig | ||
} | ||
|
||
const readConfigFromEnv = (): TestConfig => { | ||
const requiredEnvVars = { | ||
AZURE_CLIENT_ID: process.env.AZURE_CLIENT_ID, | ||
AZURE_CLIENT_SECRET: process.env.AZURE_CLIENT_SECRET, | ||
AZURE_AUTHORITY: process.env.AZURE_AUTHORITY, | ||
AZURE_TENANT_ID: process.env.AZURE_TENANT_ID, | ||
AZURE_REDIS_SCOPES: process.env.AZURE_REDIS_SCOPES, | ||
AZURE_CERT: process.env.AZURE_CERT, | ||
AZURE_PRIVATE_KEY: process.env.AZURE_PRIVATE_KEY, | ||
AZURE_USER_ASSIGNED_MANAGED_ID: process.env.AZURE_USER_ASSIGNED_MANAGED_ID, | ||
REDIS_ENDPOINTS_CONFIG_PATH: process.env.REDIS_ENDPOINTS_CONFIG_PATH | ||
}; | ||
|
||
Object.entries(requiredEnvVars).forEach(([key, value]) => { | ||
console.log(`key: ${key}, value: ${value}`); | ||
if (value == undefined) { | ||
throw new Error(`${key} environment variable must be set`); | ||
} | ||
}); | ||
|
||
return { | ||
endpoints: loadFromJson(requiredEnvVars.REDIS_ENDPOINTS_CONFIG_PATH), | ||
clientId: requiredEnvVars.AZURE_CLIENT_ID, | ||
clientSecret: requiredEnvVars.AZURE_CLIENT_SECRET, | ||
authority: requiredEnvVars.AZURE_AUTHORITY, | ||
tenantId: requiredEnvVars.AZURE_TENANT_ID, | ||
redisScopes: requiredEnvVars.AZURE_REDIS_SCOPES, | ||
cert: requiredEnvVars.AZURE_CERT, | ||
privateKey: requiredEnvVars.AZURE_PRIVATE_KEY, | ||
userAssignedManagedId: requiredEnvVars.AZURE_USER_ASSIGNED_MANAGED_ID | ||
}; | ||
}; | ||
|
||
it('client configured with with a client secret should be able to authenticate/re-authenticate', async () => { | ||
|
||
const { clientId, clientSecret, tenantId, endpoints } = readConfigFromEnv(); | ||
|
||
const entraidCredentialsProvider = EntraIdCredentialsProviderFactory.createForClientCredentials({ | ||
clientId: clientId, | ||
clientSecret: clientSecret, | ||
authorityConfig: { type: 'multi-tenant', tenantId: tenantId }, | ||
tokenManagerConfig: { | ||
expirationRefreshRatio: 0.0001 | ||
} | ||
}); | ||
|
||
const client = createClient({ | ||
url: endpoints['standalone-entraid-acl'].endpoints[0], | ||
credentialsProvider: entraidCredentialsProvider | ||
}); | ||
|
||
const clientInstance = (client as any)._self; | ||
const reAuthSpy: SinonSpy = spy(clientInstance, <any>'reAuthenticate'); | ||
|
||
try { | ||
await client.connect(); | ||
|
||
const startTime = Date.now(); | ||
while (Date.now() - startTime < 1000) { | ||
const key = randomUUID(); | ||
await client.set(key, 'value'); | ||
const value = await client.get(key); | ||
assert.equal(value, 'value'); | ||
await client.del(key); | ||
} | ||
|
||
assert(reAuthSpy.callCount >= 1, `reAuthenticate should have been called at least once, but was called ${reAuthSpy.callCount} times`); | ||
|
||
const tokenDetails = reAuthSpy.getCalls().map(call => { | ||
const creds = call.args[0] as BasicAuth; | ||
const tokenPayload = JSON.parse( | ||
Buffer.from(creds.password.split('.')[1], 'base64').toString() | ||
); | ||
|
||
return { | ||
token: creds.password, | ||
exp: tokenPayload.exp, | ||
iat: tokenPayload.iat, | ||
lifetime: tokenPayload.exp - tokenPayload.iat, | ||
uti: tokenPayload.uti | ||
}; | ||
}); | ||
|
||
// Verify unique tokens | ||
const uniqueTokens = new Set(tokenDetails.map(detail => detail.token)); | ||
assert.equal( | ||
uniqueTokens.size, | ||
reAuthSpy.callCount, | ||
`Expected ${reAuthSpy.callCount} different tokens, but got ${uniqueTokens.size} unique tokens` | ||
); | ||
|
||
// Verify all tokens are not cached (i.e. have the same lifetime) | ||
const uniqueLifetimes = new Set(tokenDetails.map(detail => detail.lifetime)); | ||
assert.equal( | ||
uniqueLifetimes.size, | ||
1, | ||
`Expected all tokens to have the same lifetime, but found ${uniqueLifetimes.size} different lifetimes: ${[uniqueLifetimes].join(', ')} seconds` | ||
); | ||
|
||
// Verify that all tokens have different uti ( unique token identifier) | ||
const uniqueUti = new Set(tokenDetails.map(detail => detail.uti)); | ||
assert.equal( | ||
uniqueUti.size, | ||
reAuthSpy.callCount, | ||
`Expected all tokens to have different uti, but found ${uniqueUti.size} different uti in: ${[uniqueUti].join(', ')}` | ||
); | ||
|
||
} finally { | ||
await client.destroy(); | ||
} | ||
}); | ||
}); |
This file contains 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 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 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 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 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,10 @@ | ||
{ | ||
"extends": "./tsconfig.json", | ||
"include": [ | ||
"./integration-tests/**/*.ts", | ||
"./lib/**/*.ts" | ||
], | ||
"compilerOptions": { | ||
"noEmit": true | ||
}, | ||
} |
This file contains 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 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,10 @@ | ||
{ | ||
"extends": "./tsconfig.json", | ||
"include": [ | ||
"./samples/**/*.ts", | ||
"./lib/**/*.ts" | ||
], | ||
"compilerOptions": { | ||
"noEmit": true | ||
} | ||
} |
This file contains 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,16 @@ | ||
interface RawRedisEndpoint { | ||
username?: string; | ||
password?: string; | ||
tls: boolean; | ||
endpoints: string[]; | ||
} | ||
|
||
export type RedisEndpointsConfig = Record<string, RawRedisEndpoint>; | ||
|
||
export function loadFromJson(jsonString: string): RedisEndpointsConfig { | ||
try { | ||
return JSON.parse(jsonString) as RedisEndpointsConfig; | ||
} catch (error) { | ||
throw new Error(`Invalid JSON configuration: ${error}`); | ||
} | ||
} |