mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 20:21:09 +02:00
🐛 fix: add missing jwt import in better-auth.service
This commit is contained in:
parent
efb077b9ea
commit
c0117b2699
4 changed files with 149 additions and 154 deletions
|
|
@ -43,7 +43,6 @@
|
|||
"duckdb-async": "^1.1.1",
|
||||
"helmet": "^8.0.0",
|
||||
"jose": "^6.1.2",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"nanoid": "^5.0.9",
|
||||
"nodemailer": "^7.0.12",
|
||||
"postgres": "^3.4.5",
|
||||
|
|
@ -64,7 +63,6 @@
|
|||
"@types/cookie-parser": "^1.4.7",
|
||||
"@types/express": "^5.0.0",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/jsonwebtoken": "^9.0.7",
|
||||
"@types/node": "^22.10.2",
|
||||
"@types/nodemailer": "^7.0.5",
|
||||
"@types/supertest": "^6.0.2",
|
||||
|
|
|
|||
|
|
@ -16,11 +16,14 @@
|
|||
* 2. Organization context available via Better Auth org plugin APIs
|
||||
* 3. Smaller tokens = better performance
|
||||
* 4. Follows Better Auth's session-based design
|
||||
*
|
||||
* NOTE: These tests use jose library (EdDSA/HS256) as per project guidelines.
|
||||
* Production uses EdDSA via Better Auth's JWKS.
|
||||
*/
|
||||
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import * as jwt from 'jsonwebtoken';
|
||||
import { SignJWT, jwtVerify, errors } from 'jose';
|
||||
import { JWTCustomPayload } from './better-auth.config';
|
||||
import { createMockConfigService } from '../__tests__/utils/test-helpers';
|
||||
import { mockUserFactory } from '../__tests__/utils/mock-factories';
|
||||
|
|
@ -31,15 +34,55 @@ jest.mock('nanoid', () => ({
|
|||
nanoid: jest.fn(() => 'mock-nanoid-123'),
|
||||
}));
|
||||
|
||||
// Helper to create JWT using jose
|
||||
async function signJwt(
|
||||
payload: JWTCustomPayload,
|
||||
secret: Uint8Array,
|
||||
options: { expiresIn?: string; issuer?: string; audience?: string; notBefore?: number } = {}
|
||||
): Promise<string> {
|
||||
const jwt = new SignJWT(payload as unknown as Record<string, unknown>)
|
||||
.setProtectedHeader({ alg: 'HS256' })
|
||||
.setIssuedAt();
|
||||
|
||||
if (options.expiresIn) {
|
||||
jwt.setExpirationTime(options.expiresIn);
|
||||
}
|
||||
if (options.issuer) {
|
||||
jwt.setIssuer(options.issuer);
|
||||
}
|
||||
if (options.audience) {
|
||||
jwt.setAudience(options.audience);
|
||||
}
|
||||
if (options.notBefore !== undefined) {
|
||||
jwt.setNotBefore(options.notBefore);
|
||||
}
|
||||
|
||||
return jwt.sign(secret);
|
||||
}
|
||||
|
||||
// Helper to verify JWT using jose
|
||||
async function verifyJwt(
|
||||
token: string,
|
||||
secret: Uint8Array,
|
||||
options: { issuer?: string; audience?: string } = {}
|
||||
): Promise<JWTCustomPayload> {
|
||||
const { payload } = await jwtVerify(token, secret, {
|
||||
algorithms: ['HS256'],
|
||||
issuer: options.issuer,
|
||||
audience: options.audience,
|
||||
});
|
||||
return payload as unknown as JWTCustomPayload;
|
||||
}
|
||||
|
||||
describe('JWT Token Validation (Minimal Claims)', () => {
|
||||
let configService: ConfigService;
|
||||
let mockDb: any;
|
||||
let secret: string;
|
||||
let secret: Uint8Array;
|
||||
|
||||
beforeEach(async () => {
|
||||
// Use HS256 for testing (symmetric key) for simplicity
|
||||
// In production, mana-core uses RS256 (asymmetric)
|
||||
secret = 'test-secret-key-for-jwt-validation';
|
||||
// In production, mana-core uses EdDSA via Better Auth's JWKS
|
||||
secret = new TextEncoder().encode('test-secret-key-for-jwt-validation-must-be-32-chars');
|
||||
|
||||
// Create mock database
|
||||
mockDb = {
|
||||
|
|
@ -60,7 +103,6 @@ describe('JWT Token Validation (Minimal Claims)', () => {
|
|||
getDb.mockReturnValue(mockDb);
|
||||
|
||||
configService = createMockConfigService({
|
||||
'jwt.secret': secret,
|
||||
'jwt.issuer': 'mana-core',
|
||||
'jwt.audience': 'manacore',
|
||||
});
|
||||
|
|
@ -71,7 +113,7 @@ describe('JWT Token Validation (Minimal Claims)', () => {
|
|||
});
|
||||
|
||||
describe('Minimal JWT Claims Structure', () => {
|
||||
it('should generate token with minimal claims only', () => {
|
||||
it('should generate token with minimal claims only', async () => {
|
||||
const user = mockUserFactory.create({
|
||||
id: 'user-123',
|
||||
email: 'user@example.com',
|
||||
|
|
@ -85,18 +127,16 @@ describe('JWT Token Validation (Minimal Claims)', () => {
|
|||
sid: 'session-abc-123',
|
||||
};
|
||||
|
||||
const token = jwt.sign(payload, secret, {
|
||||
algorithm: 'HS256',
|
||||
const token = await signJwt(payload, secret, {
|
||||
expiresIn: '15m',
|
||||
issuer: 'mana-core',
|
||||
audience: 'manacore',
|
||||
});
|
||||
|
||||
const decoded = jwt.verify(token, secret, {
|
||||
algorithms: ['HS256'],
|
||||
const decoded = await verifyJwt(token, secret, {
|
||||
issuer: 'mana-core',
|
||||
audience: 'manacore',
|
||||
}) as JWTCustomPayload;
|
||||
});
|
||||
|
||||
expect(decoded).toMatchObject({
|
||||
sub: 'user-123',
|
||||
|
|
@ -113,7 +153,7 @@ describe('JWT Token Validation (Minimal Claims)', () => {
|
|||
expect((decoded as any).device_id).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should include standard JWT claims (sub, iat, exp, iss, aud)', () => {
|
||||
it('should include standard JWT claims (sub, iat, exp, iss, aud)', async () => {
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
|
||||
const payload: JWTCustomPayload = {
|
||||
|
|
@ -123,29 +163,26 @@ describe('JWT Token Validation (Minimal Claims)', () => {
|
|||
sid: 'session-123',
|
||||
};
|
||||
|
||||
const token = jwt.sign(payload, secret, {
|
||||
algorithm: 'HS256',
|
||||
const token = await signJwt(payload, secret, {
|
||||
expiresIn: '15m',
|
||||
issuer: 'mana-core',
|
||||
audience: 'manacore',
|
||||
});
|
||||
|
||||
const decoded: any = jwt.verify(token, secret, {
|
||||
algorithms: ['HS256'],
|
||||
});
|
||||
const decoded = await verifyJwt(token, secret);
|
||||
|
||||
// Standard JWT claims
|
||||
expect(decoded.sub).toBe('user-123');
|
||||
expect(decoded.iat).toBeGreaterThanOrEqual(now);
|
||||
expect(decoded.exp).toBeGreaterThan(decoded.iat);
|
||||
expect(decoded.iss).toBe('mana-core');
|
||||
expect(decoded.aud).toBe('manacore');
|
||||
expect((decoded as any).iat).toBeGreaterThanOrEqual(now);
|
||||
expect((decoded as any).exp).toBeGreaterThan((decoded as any).iat);
|
||||
expect((decoded as any).iss).toBe('mana-core');
|
||||
expect((decoded as any).aud).toBe('manacore');
|
||||
});
|
||||
|
||||
it('should support different user roles', () => {
|
||||
it('should support different user roles', async () => {
|
||||
const roles = ['user', 'admin', 'service'];
|
||||
|
||||
roles.forEach((role) => {
|
||||
for (const role of roles) {
|
||||
const payload: JWTCustomPayload = {
|
||||
sub: `${role}-user-123`,
|
||||
email: `${role}@example.com`,
|
||||
|
|
@ -153,24 +190,21 @@ describe('JWT Token Validation (Minimal Claims)', () => {
|
|||
sid: `session-${role}`,
|
||||
};
|
||||
|
||||
const token = jwt.sign(payload, secret, {
|
||||
algorithm: 'HS256',
|
||||
const token = await signJwt(payload, secret, {
|
||||
expiresIn: '15m',
|
||||
issuer: 'mana-core',
|
||||
audience: 'manacore',
|
||||
});
|
||||
|
||||
const decoded = jwt.verify(token, secret, {
|
||||
algorithms: ['HS256'],
|
||||
}) as JWTCustomPayload;
|
||||
const decoded = await verifyJwt(token, secret);
|
||||
|
||||
expect(decoded.role).toBe(role);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Token Validation - Security', () => {
|
||||
it('should validate HS256 signature correctly', () => {
|
||||
it('should validate HS256 signature correctly', async () => {
|
||||
const payload: JWTCustomPayload = {
|
||||
sub: 'user-123',
|
||||
email: 'user@example.com',
|
||||
|
|
@ -178,22 +212,17 @@ describe('JWT Token Validation (Minimal Claims)', () => {
|
|||
sid: 'session-123',
|
||||
};
|
||||
|
||||
const token = jwt.sign(payload, secret, {
|
||||
algorithm: 'HS256',
|
||||
const token = await signJwt(payload, secret, {
|
||||
expiresIn: '15m',
|
||||
issuer: 'mana-core',
|
||||
audience: 'manacore',
|
||||
});
|
||||
|
||||
// Should successfully verify with correct secret
|
||||
expect(() => {
|
||||
jwt.verify(token, secret, {
|
||||
algorithms: ['HS256'],
|
||||
});
|
||||
}).not.toThrow();
|
||||
await expect(verifyJwt(token, secret)).resolves.toBeDefined();
|
||||
});
|
||||
|
||||
it('should reject expired tokens', () => {
|
||||
it('should reject expired tokens', async () => {
|
||||
const payload: JWTCustomPayload = {
|
||||
sub: 'user-123',
|
||||
email: 'user@example.com',
|
||||
|
|
@ -202,27 +231,19 @@ describe('JWT Token Validation (Minimal Claims)', () => {
|
|||
};
|
||||
|
||||
// Create token that expires immediately
|
||||
const token = jwt.sign(payload, secret, {
|
||||
algorithm: 'HS256',
|
||||
const token = await signJwt(payload, secret, {
|
||||
expiresIn: '0s', // Expired immediately
|
||||
issuer: 'mana-core',
|
||||
audience: 'manacore',
|
||||
});
|
||||
|
||||
// Wait a moment to ensure expiry
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
expect(() => {
|
||||
jwt.verify(token, secret, {
|
||||
algorithms: ['HS256'],
|
||||
});
|
||||
}).toThrow('jwt expired');
|
||||
resolve(true);
|
||||
}, 100);
|
||||
});
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
await expect(verifyJwt(token, secret)).rejects.toThrow(errors.JWTExpired);
|
||||
});
|
||||
|
||||
it('should reject tokens with wrong issuer', () => {
|
||||
it('should reject tokens with wrong issuer', async () => {
|
||||
const payload: JWTCustomPayload = {
|
||||
sub: 'user-123',
|
||||
email: 'user@example.com',
|
||||
|
|
@ -230,23 +251,21 @@ describe('JWT Token Validation (Minimal Claims)', () => {
|
|||
sid: 'session-123',
|
||||
};
|
||||
|
||||
const token = jwt.sign(payload, secret, {
|
||||
algorithm: 'HS256',
|
||||
const token = await signJwt(payload, secret, {
|
||||
expiresIn: '15m',
|
||||
issuer: 'wrong-issuer', // Wrong issuer
|
||||
audience: 'manacore',
|
||||
});
|
||||
|
||||
expect(() => {
|
||||
jwt.verify(token, secret, {
|
||||
algorithms: ['HS256'],
|
||||
await expect(
|
||||
verifyJwt(token, secret, {
|
||||
issuer: 'mana-core', // Expect correct issuer
|
||||
audience: 'manacore',
|
||||
});
|
||||
}).toThrow('jwt issuer invalid');
|
||||
})
|
||||
).rejects.toThrow(errors.JWTClaimValidationFailed);
|
||||
});
|
||||
|
||||
it('should reject tokens with wrong audience', () => {
|
||||
it('should reject tokens with wrong audience', async () => {
|
||||
const payload: JWTCustomPayload = {
|
||||
sub: 'user-123',
|
||||
email: 'user@example.com',
|
||||
|
|
@ -254,23 +273,21 @@ describe('JWT Token Validation (Minimal Claims)', () => {
|
|||
sid: 'session-123',
|
||||
};
|
||||
|
||||
const token = jwt.sign(payload, secret, {
|
||||
algorithm: 'HS256',
|
||||
const token = await signJwt(payload, secret, {
|
||||
expiresIn: '15m',
|
||||
issuer: 'mana-core',
|
||||
audience: 'wrong-audience', // Wrong audience
|
||||
});
|
||||
|
||||
expect(() => {
|
||||
jwt.verify(token, secret, {
|
||||
algorithms: ['HS256'],
|
||||
await expect(
|
||||
verifyJwt(token, secret, {
|
||||
issuer: 'mana-core',
|
||||
audience: 'manacore', // Expect correct audience
|
||||
});
|
||||
}).toThrow('jwt audience invalid');
|
||||
})
|
||||
).rejects.toThrow(errors.JWTClaimValidationFailed);
|
||||
});
|
||||
|
||||
it('should reject tampered tokens', () => {
|
||||
it('should reject tampered tokens', async () => {
|
||||
const payload: JWTCustomPayload = {
|
||||
sub: 'user-123',
|
||||
email: 'user@example.com',
|
||||
|
|
@ -278,8 +295,7 @@ describe('JWT Token Validation (Minimal Claims)', () => {
|
|||
sid: 'session-123',
|
||||
};
|
||||
|
||||
const token = jwt.sign(payload, secret, {
|
||||
algorithm: 'HS256',
|
||||
const token = await signJwt(payload, secret, {
|
||||
expiresIn: '15m',
|
||||
issuer: 'mana-core',
|
||||
audience: 'manacore',
|
||||
|
|
@ -292,14 +308,12 @@ describe('JWT Token Validation (Minimal Claims)', () => {
|
|||
);
|
||||
const tamperedToken = `${parts[0]}.${tamperedPayload}.${parts[2]}`;
|
||||
|
||||
expect(() => {
|
||||
jwt.verify(tamperedToken, secret, {
|
||||
algorithms: ['HS256'],
|
||||
});
|
||||
}).toThrow('invalid signature');
|
||||
await expect(verifyJwt(tamperedToken, secret)).rejects.toThrow(
|
||||
errors.JWSSignatureVerificationFailed
|
||||
);
|
||||
});
|
||||
|
||||
it('should reject tokens signed with wrong secret', () => {
|
||||
it('should reject tokens signed with wrong secret', async () => {
|
||||
const payload: JWTCustomPayload = {
|
||||
sub: 'user-123',
|
||||
email: 'user@example.com',
|
||||
|
|
@ -308,24 +322,21 @@ describe('JWT Token Validation (Minimal Claims)', () => {
|
|||
};
|
||||
|
||||
// Sign with different secret
|
||||
const token = jwt.sign(payload, 'wrong-secret-key', {
|
||||
algorithm: 'HS256',
|
||||
const wrongSecret = new TextEncoder().encode('wrong-secret-key-for-testing-wrong');
|
||||
|
||||
const token = await signJwt(payload, wrongSecret, {
|
||||
expiresIn: '15m',
|
||||
issuer: 'mana-core',
|
||||
audience: 'manacore',
|
||||
});
|
||||
|
||||
// Try to verify with correct secret
|
||||
expect(() => {
|
||||
jwt.verify(token, secret, {
|
||||
algorithms: ['HS256'],
|
||||
});
|
||||
}).toThrow();
|
||||
await expect(verifyJwt(token, secret)).rejects.toThrow(errors.JWSSignatureVerificationFailed);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Token Expiration Times', () => {
|
||||
it('should use 15 minutes for access tokens', () => {
|
||||
it('should use 15 minutes for access tokens', async () => {
|
||||
const payload: JWTCustomPayload = {
|
||||
sub: 'user-123',
|
||||
email: 'user@example.com',
|
||||
|
|
@ -333,22 +344,19 @@ describe('JWT Token Validation (Minimal Claims)', () => {
|
|||
sid: 'session-123',
|
||||
};
|
||||
|
||||
const token = jwt.sign(payload, secret, {
|
||||
algorithm: 'HS256',
|
||||
const token = await signJwt(payload, secret, {
|
||||
expiresIn: '15m',
|
||||
issuer: 'mana-core',
|
||||
audience: 'manacore',
|
||||
});
|
||||
|
||||
const decoded: any = jwt.verify(token, secret, {
|
||||
algorithms: ['HS256'],
|
||||
});
|
||||
const decoded: any = await verifyJwt(token, secret);
|
||||
|
||||
const expiryTime = decoded.exp - decoded.iat;
|
||||
expect(expiryTime).toBe(15 * 60); // 15 minutes = 900 seconds
|
||||
});
|
||||
|
||||
it('should validate token is not yet valid (nbf claim)', () => {
|
||||
it('should validate token is not yet valid (nbf claim)', async () => {
|
||||
const futureTime = Math.floor(Date.now() / 1000) + 3600; // 1 hour in future
|
||||
|
||||
const payload: JWTCustomPayload = {
|
||||
|
|
@ -358,56 +366,40 @@ describe('JWT Token Validation (Minimal Claims)', () => {
|
|||
sid: 'session-123',
|
||||
};
|
||||
|
||||
const token = jwt.sign(payload, secret, {
|
||||
algorithm: 'HS256',
|
||||
const token = await signJwt(payload, secret, {
|
||||
expiresIn: '15m',
|
||||
notBefore: futureTime, // Not valid until 1 hour from now
|
||||
issuer: 'mana-core',
|
||||
audience: 'manacore',
|
||||
});
|
||||
|
||||
expect(() => {
|
||||
jwt.verify(token, secret, {
|
||||
algorithms: ['HS256'],
|
||||
});
|
||||
}).toThrow('jwt not active');
|
||||
await expect(verifyJwt(token, secret)).rejects.toThrow(errors.JWTClaimValidationFailed);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edge Cases', () => {
|
||||
it('should handle malformed JWT gracefully', () => {
|
||||
it('should handle malformed JWT gracefully', async () => {
|
||||
const malformedToken = 'this.is.not.a.valid.jwt';
|
||||
|
||||
expect(() => {
|
||||
jwt.verify(malformedToken, secret, {
|
||||
algorithms: ['HS256'],
|
||||
});
|
||||
}).toThrow('jwt malformed');
|
||||
await expect(verifyJwt(malformedToken, secret)).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('should handle empty token', () => {
|
||||
expect(() => {
|
||||
jwt.verify('', secret, {
|
||||
algorithms: ['HS256'],
|
||||
});
|
||||
}).toThrow('jwt must be provided');
|
||||
it('should handle empty token', async () => {
|
||||
await expect(verifyJwt('', secret)).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('should handle token with missing required claims', () => {
|
||||
it('should handle token with missing required claims', async () => {
|
||||
// Token with only sub (missing email, role, sid)
|
||||
const minimalPayload = { sub: 'user-123' };
|
||||
const minimalPayload = { sub: 'user-123' } as unknown as JWTCustomPayload;
|
||||
|
||||
const token = jwt.sign(minimalPayload, secret, {
|
||||
algorithm: 'HS256',
|
||||
const token = await signJwt(minimalPayload, secret, {
|
||||
expiresIn: '15m',
|
||||
issuer: 'mana-core',
|
||||
audience: 'manacore',
|
||||
});
|
||||
|
||||
// Token is technically valid, but application should validate claims
|
||||
const decoded = jwt.verify(token, secret, {
|
||||
algorithms: ['HS256'],
|
||||
}) as any;
|
||||
const decoded = await verifyJwt(token, secret);
|
||||
|
||||
expect(decoded.sub).toBe('user-123');
|
||||
expect(decoded.email).toBeUndefined();
|
||||
|
|
@ -417,7 +409,7 @@ describe('JWT Token Validation (Minimal Claims)', () => {
|
|||
});
|
||||
|
||||
describe('Token Refresh Behavior', () => {
|
||||
it('should issue new token with same user claims', () => {
|
||||
it('should issue new token with same user claims', async () => {
|
||||
const originalPayload: JWTCustomPayload = {
|
||||
sub: 'user-123',
|
||||
email: 'user@example.com',
|
||||
|
|
@ -425,8 +417,7 @@ describe('JWT Token Validation (Minimal Claims)', () => {
|
|||
sid: 'session-original',
|
||||
};
|
||||
|
||||
const originalToken = jwt.sign(originalPayload, secret, {
|
||||
algorithm: 'HS256',
|
||||
const originalToken = await signJwt(originalPayload, secret, {
|
||||
expiresIn: '15m',
|
||||
issuer: 'mana-core',
|
||||
audience: 'manacore',
|
||||
|
|
@ -438,16 +429,13 @@ describe('JWT Token Validation (Minimal Claims)', () => {
|
|||
sid: 'session-refreshed', // New session ID
|
||||
};
|
||||
|
||||
const refreshedToken = jwt.sign(refreshedPayload, secret, {
|
||||
algorithm: 'HS256',
|
||||
const refreshedToken = await signJwt(refreshedPayload, secret, {
|
||||
expiresIn: '15m',
|
||||
issuer: 'mana-core',
|
||||
audience: 'manacore',
|
||||
});
|
||||
|
||||
const decoded = jwt.verify(refreshedToken, secret, {
|
||||
algorithms: ['HS256'],
|
||||
}) as JWTCustomPayload;
|
||||
const decoded = await verifyJwt(refreshedToken, secret);
|
||||
|
||||
// User claims should be maintained
|
||||
expect(decoded.sub).toBe('user-123');
|
||||
|
|
@ -457,7 +445,7 @@ describe('JWT Token Validation (Minimal Claims)', () => {
|
|||
expect(decoded.sid).toBe('session-refreshed');
|
||||
});
|
||||
|
||||
it('should maintain user role across refreshes', () => {
|
||||
it('should maintain user role across refreshes', async () => {
|
||||
const adminPayload: JWTCustomPayload = {
|
||||
sub: 'admin-123',
|
||||
email: 'admin@example.com',
|
||||
|
|
@ -465,16 +453,13 @@ describe('JWT Token Validation (Minimal Claims)', () => {
|
|||
sid: 'session-123',
|
||||
};
|
||||
|
||||
const token = jwt.sign(adminPayload, secret, {
|
||||
algorithm: 'HS256',
|
||||
const token = await signJwt(adminPayload, secret, {
|
||||
expiresIn: '15m',
|
||||
issuer: 'mana-core',
|
||||
audience: 'manacore',
|
||||
});
|
||||
|
||||
const decoded = jwt.verify(token, secret, {
|
||||
algorithms: ['HS256'],
|
||||
}) as JWTCustomPayload;
|
||||
const decoded = await verifyJwt(token, secret);
|
||||
|
||||
// Admin role should be preserved
|
||||
expect(decoded.role).toBe('admin');
|
||||
|
|
@ -486,7 +471,7 @@ describe('JWT Token Validation (Minimal Claims)', () => {
|
|||
* This test documents what is NOT in the JWT by design.
|
||||
* See docs/AUTHENTICATION_ARCHITECTURE.md for full explanation.
|
||||
*/
|
||||
it('should NOT contain organization data (fetch via API instead)', () => {
|
||||
it('should NOT contain organization data (fetch via API instead)', async () => {
|
||||
const payload: JWTCustomPayload = {
|
||||
sub: 'user-123',
|
||||
email: 'user@example.com',
|
||||
|
|
@ -494,25 +479,22 @@ describe('JWT Token Validation (Minimal Claims)', () => {
|
|||
sid: 'session-123',
|
||||
};
|
||||
|
||||
const token = jwt.sign(payload, secret, {
|
||||
algorithm: 'HS256',
|
||||
const token = await signJwt(payload, secret, {
|
||||
expiresIn: '15m',
|
||||
issuer: 'mana-core',
|
||||
audience: 'manacore',
|
||||
});
|
||||
|
||||
const decoded = jwt.verify(token, secret, {
|
||||
algorithms: ['HS256'],
|
||||
}) as any;
|
||||
const decoded = await verifyJwt(token, secret);
|
||||
|
||||
// Organization data should be fetched via:
|
||||
// - session.activeOrganizationId (from Better Auth session)
|
||||
// - GET /organization/get-active-member (for details)
|
||||
expect(decoded.organization).toBeUndefined();
|
||||
expect(decoded.organizationId).toBeUndefined();
|
||||
expect((decoded as any).organization).toBeUndefined();
|
||||
expect((decoded as any).organizationId).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should NOT contain credit balance (fetch via API instead)', () => {
|
||||
it('should NOT contain credit balance (fetch via API instead)', async () => {
|
||||
const payload: JWTCustomPayload = {
|
||||
sub: 'user-123',
|
||||
email: 'user@example.com',
|
||||
|
|
@ -520,25 +502,22 @@ describe('JWT Token Validation (Minimal Claims)', () => {
|
|||
sid: 'session-123',
|
||||
};
|
||||
|
||||
const token = jwt.sign(payload, secret, {
|
||||
algorithm: 'HS256',
|
||||
const token = await signJwt(payload, secret, {
|
||||
expiresIn: '15m',
|
||||
issuer: 'mana-core',
|
||||
audience: 'manacore',
|
||||
});
|
||||
|
||||
const decoded = jwt.verify(token, secret, {
|
||||
algorithms: ['HS256'],
|
||||
}) as any;
|
||||
const decoded = await verifyJwt(token, secret);
|
||||
|
||||
// Credit balance should be fetched via:
|
||||
// - GET /api/v1/credits/balance
|
||||
// Credit balance changes too frequently to embed in JWT
|
||||
expect(decoded.credit_balance).toBeUndefined();
|
||||
expect(decoded.credits).toBeUndefined();
|
||||
expect((decoded as any).credit_balance).toBeUndefined();
|
||||
expect((decoded as any).credits).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should NOT contain customer_type (derive from session instead)', () => {
|
||||
it('should NOT contain customer_type (derive from session instead)', async () => {
|
||||
const payload: JWTCustomPayload = {
|
||||
sub: 'user-123',
|
||||
email: 'user@example.com',
|
||||
|
|
@ -546,21 +525,18 @@ describe('JWT Token Validation (Minimal Claims)', () => {
|
|||
sid: 'session-123',
|
||||
};
|
||||
|
||||
const token = jwt.sign(payload, secret, {
|
||||
algorithm: 'HS256',
|
||||
const token = await signJwt(payload, secret, {
|
||||
expiresIn: '15m',
|
||||
issuer: 'mana-core',
|
||||
audience: 'manacore',
|
||||
});
|
||||
|
||||
const decoded = jwt.verify(token, secret, {
|
||||
algorithms: ['HS256'],
|
||||
}) as any;
|
||||
const decoded = await verifyJwt(token, secret);
|
||||
|
||||
// Customer type should be derived from:
|
||||
// - B2B = session.activeOrganizationId != null
|
||||
// - B2C = session.activeOrganizationId == null
|
||||
expect(decoded.customer_type).toBeUndefined();
|
||||
expect((decoded as any).customer_type).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ import type {
|
|||
BetterAuthSession,
|
||||
} from '../types/better-auth.types';
|
||||
import { jwtVerify, createRemoteJWKSet } from 'jose';
|
||||
import * as jwt from 'jsonwebtoken';
|
||||
|
||||
// Re-export DTOs and result types for external use
|
||||
export type {
|
||||
|
|
|
|||
20
services/mana-core-auth/test/__mocks__/jose.ts
Normal file
20
services/mana-core-auth/test/__mocks__/jose.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
* Jose Mock - Re-exports the real module functions
|
||||
*
|
||||
* We use the real jose library for JWT validation tests
|
||||
* since we're testing actual JWT creation and verification.
|
||||
*
|
||||
* Note: We need to explicitly require and re-export because
|
||||
* jest module mocking doesn't handle ESM re-exports well.
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
const jose = require('jose');
|
||||
|
||||
export const SignJWT = jose.SignJWT;
|
||||
export const jwtVerify = jose.jwtVerify;
|
||||
export const createRemoteJWKSet = jose.createRemoteJWKSet;
|
||||
export const errors = jose.errors;
|
||||
export const generateKeyPair = jose.generateKeyPair;
|
||||
export const exportJWK = jose.exportJWK;
|
||||
export const importJWK = jose.importJWK;
|
||||
Loading…
Add table
Add a link
Reference in a new issue