kimi-code / packages /kap-server /test /authTokenStore.test.ts
SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
4e23b01 verified
Raw History Blame Contribute Delete
8.55 kB
import {
chmodSync,
existsSync,
mkdtempSync,
readFileSync,
rmSync,
statSync,
writeFileSync,
} from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import {
PrivateFileTooPermissiveError,
readPrivateFile,
writePrivateFile,
} from '../src/services/auth/privateFiles';
import {
loadOrCreateServerToken,
rotateServerToken,
} from '../src/services/auth/persistentToken';
import { createTokenStore } from '../src/services/auth/tokenStore';
import { createAuthTokenService } from '../src/services/auth/authTokenService';
import { resolvePasswordHash, verifyPassword } from '../src/services/auth/password';
let tmpDir: string;
beforeEach(() => {
tmpDir = mkdtempSync(join(tmpdir(), 'kimi-server-v2-auth-token-'));
});
afterEach(() => {
rmSync(tmpDir, { recursive: true, force: true });
});
describe('privateFiles', () => {
it.skipIf(process.platform === 'win32')('writes a file with mode 0600', async () => {
const p = join(tmpDir, 'secret');
await writePrivateFile(p, 'hello');
expect(statSync(p).mode & 0o777).toBe(0o600);
});
it.skipIf(process.platform === 'win32')('creates an absent parent dir with mode 0700', async () => {
const p = join(tmpDir, 'nested', 'dir', 'secret');
await writePrivateFile(p, 'hello');
expect(statSync(join(tmpDir, 'nested', 'dir')).mode & 0o777).toBe(0o700);
});
it('round-trips string content through readPrivateFile', async () => {
const p = join(tmpDir, 'secret');
await writePrivateFile(p, 's3cr3t-value');
const buf = await readPrivateFile(p);
expect(buf.toString('utf8')).toBe('s3cr3t-value');
});
it('round-trips Buffer content through readPrivateFile', async () => {
const p = join(tmpDir, 'bin');
const data = Buffer.from([0, 1, 2, 254, 255]);
await writePrivateFile(p, data);
const buf = await readPrivateFile(p);
expect(buf.equals(data)).toBe(true);
});
it.skipIf(process.platform === 'win32')('readPrivateFile throws on a 0644 file', async () => {
const p = join(tmpDir, 'leaky');
writeFileSync(p, 'x', { mode: 0o644 });
chmodSync(p, 0o644);
await expect(readPrivateFile(p)).rejects.toThrowError(PrivateFileTooPermissiveError);
});
});
describe('tokenStore', () => {
it('returns the same token from repeated getToken() calls', async () => {
const store = await createTokenStore(join(tmpDir, 'home'));
expect(store.getToken()).toBe(store.getToken());
await store.dispose();
});
it('produces different tokens for different home dirs', async () => {
const a = await createTokenStore(join(tmpDir, 'home-a'));
const b = await createTokenStore(join(tmpDir, 'home-b'));
expect(a.getToken()).not.toBe(b.getToken());
await a.dispose();
await b.dispose();
});
it('reuses the same persistent token across stores in one home dir', async () => {
const home = join(tmpDir, 'home');
const a = await createTokenStore(home);
const token = a.getToken();
await a.dispose();
const b = await createTokenStore(home);
expect(b.getToken()).toBe(token);
await b.dispose();
});
it.skipIf(process.platform === 'win32')('writes the token file with mode 0600 at server.token', async () => {
const home = join(tmpDir, 'home');
const store = await createTokenStore(home);
expect(store.tokenPath).toBe(join(home, 'server.token'));
expect(statSync(store.tokenPath).mode & 0o777).toBe(0o600);
await store.dispose();
});
it('isValid accepts the token and rejects wrong / empty / same-length candidates', async () => {
const store = await createTokenStore(join(tmpDir, 'home'));
const token = store.getToken();
expect(store.isValid(token)).toBe(true);
expect(store.isValid('wrong')).toBe(false);
expect(store.isValid('')).toBe(false);
const other = await createTokenStore(join(tmpDir, 'home-other'));
expect(other.getToken().length).toBe(token.length);
expect(store.isValid(other.getToken())).toBe(false);
await store.dispose();
await other.dispose();
});
it('dispose() keeps the persistent token file on disk', async () => {
const store = await createTokenStore(join(tmpDir, 'home'));
expect(existsSync(store.tokenPath)).toBe(true);
await store.dispose();
expect(existsSync(store.tokenPath)).toBe(true);
});
it('re-reads the token after the file is rewritten (live rotation)', async () => {
const home = join(tmpDir, 'home');
const store = await createTokenStore(home);
const original = store.getToken();
const rotated = 'r'.repeat(original.length);
await writePrivateFile(store.tokenPath, rotated);
expect(store.getToken()).toBe(rotated);
expect(store.isValid(rotated)).toBe(true);
expect(store.isValid(original)).toBe(false);
await store.dispose();
});
});
describe('persistentToken', () => {
it('loadOrCreateServerToken generates once and reuses thereafter', async () => {
const home = join(tmpDir, 'home');
const a = await loadOrCreateServerToken(home);
const b = await loadOrCreateServerToken(home);
expect(a).toBe(b);
});
it.skipIf(process.platform === 'win32')('writes server.token with mode 0600', async () => {
const home = join(tmpDir, 'home');
await loadOrCreateServerToken(home);
expect(statSync(join(home, 'server.token')).mode & 0o777).toBe(0o600);
});
it('rotateServerToken writes a new, different token to server.token', async () => {
const home = join(tmpDir, 'home');
const original = await loadOrCreateServerToken(home);
const rotated = await rotateServerToken(home);
expect(rotated).not.toBe(original);
expect(readFileSync(join(home, 'server.token'), 'utf8').trim()).toBe(rotated);
});
});
describe('password', () => {
it('resolvePasswordHash returns undefined when env is unset or empty', async () => {
expect(await resolvePasswordHash({})).toBeUndefined();
expect(await resolvePasswordHash({ KIMI_CODE_PASSWORD: '' })).toBeUndefined();
});
it('hashes a set password with bcrypt and verifies correctly', async () => {
const passwordHash = await resolvePasswordHash({
KIMI_CODE_PASSWORD: 'correct-horse-battery-staple',
});
expect(passwordHash?.startsWith('$2')).toBe(true);
expect(await verifyPassword('correct-horse-battery-staple', passwordHash)).toBe(true);
expect(await verifyPassword('wrong-password', passwordHash)).toBe(false);
});
it('verifyPassword returns false when the hash is undefined', async () => {
expect(await verifyPassword('anything', undefined)).toBe(false);
});
});
describe('createAuthTokenService', () => {
it('getToken() returns the tokenStore token', async () => {
const store = await createTokenStore(join(tmpDir, 'home'));
const svc = createAuthTokenService({ tokenStore: store, passwordHash: undefined });
expect(svc.getToken()).toBe(store.getToken());
await store.dispose();
});
it('isValid accepts the token', async () => {
const store = await createTokenStore(join(tmpDir, 'home'));
const svc = createAuthTokenService({ tokenStore: store, passwordHash: undefined });
expect(await svc.isValid(store.getToken())).toBe(true);
await store.dispose();
});
it('isValid accepts the password when a hash is configured', async () => {
const store = await createTokenStore(join(tmpDir, 'home'));
const passwordHash = await resolvePasswordHash({
KIMI_CODE_PASSWORD: 'correct horse battery staple',
});
const svc = createAuthTokenService({ tokenStore: store, passwordHash });
expect(await svc.isValid('correct horse battery staple')).toBe(true);
await store.dispose();
});
it('isValid rejects a wrong candidate', async () => {
const store = await createTokenStore(join(tmpDir, 'home'));
const passwordHash = await resolvePasswordHash({
KIMI_CODE_PASSWORD: 'correct horse battery staple',
});
const svc = createAuthTokenService({ tokenStore: store, passwordHash });
expect(await svc.isValid('wrong')).toBe(false);
await store.dispose();
});
it('isValid accepts only the token when passwordHash is undefined', async () => {
const store = await createTokenStore(join(tmpDir, 'home'));
const svc = createAuthTokenService({ tokenStore: store, passwordHash: undefined });
expect(await svc.isValid(store.getToken())).toBe(true);
expect(await svc.isValid('any-password')).toBe(false);
await store.dispose();
});
});