139 lines
4.8 KiB
TypeScript
139 lines
4.8 KiB
TypeScript
import { describe, it, expect, beforeEach } from 'vitest';
|
|
import { ProviderFactory } from '../src/providers/factory';
|
|
import { VkProvider } from '../src/providers/vk/vk-provider';
|
|
import { GiveawayStore } from '../src/lib/giveaway-store';
|
|
import { MemoryGiveawayRepository } from '../src/lib/repository/memory-repository';
|
|
import { NextRequest } from 'next/server';
|
|
import { POST as giveawaysPost } from '../src/app/api/giveaways/route';
|
|
import { POST as previewPost } from '../src/app/api/posts/preview/route';
|
|
import { defaultSessionStore, SESSION_COOKIE_NAME } from '../src/lib/auth/session';
|
|
import { readFileSync } from 'fs';
|
|
import { resolve } from 'path';
|
|
|
|
describe('Security: VK_SERVICE_TOKEN handling', () => {
|
|
const secretToken = 'vk1.a.super-secret-service-token-xyz';
|
|
|
|
beforeEach(() => {
|
|
GiveawayStore.setRepository(new MemoryGiveawayRepository());
|
|
});
|
|
|
|
it('ProviderFactory does not expose VK_SERVICE_TOKEN in public API', () => {
|
|
process.env.VK_SERVICE_TOKEN = secretToken;
|
|
const provider = ProviderFactory.getProvider('VK');
|
|
expect(provider.platform).toBe('VK');
|
|
expect(provider).not.toHaveProperty('serviceToken');
|
|
delete process.env.VK_SERVICE_TOKEN;
|
|
});
|
|
|
|
it('Giveaway API response does not contain VK_SERVICE_TOKEN', async () => {
|
|
process.env.VK_SERVICE_TOKEN = secretToken;
|
|
const req = new NextRequest('http://localhost/api/giveaways', {
|
|
method: 'POST',
|
|
body: JSON.stringify({
|
|
sourceUrl: 'https://vk.com/wall-1_1',
|
|
post: {
|
|
platform: 'VK',
|
|
ownerId: '-1',
|
|
postId: '1',
|
|
sourceUrl: 'https://vk.com/wall-1_1',
|
|
title: 'Test',
|
|
text: 'Test',
|
|
likesCount: 0,
|
|
commentsCount: 0,
|
|
repostsCount: 0,
|
|
},
|
|
filterRules: {
|
|
requireLike: true,
|
|
requireComment: false,
|
|
requireRepost: false,
|
|
requireSubscription: false,
|
|
excludeAdmins: false,
|
|
excludeBlacklistedIds: [],
|
|
},
|
|
}),
|
|
});
|
|
|
|
const res = await giveawaysPost(req);
|
|
const text = await res.text();
|
|
expect(text).not.toContain(secretToken);
|
|
delete process.env.VK_SERVICE_TOKEN;
|
|
});
|
|
|
|
it('Post preview response does not contain VK_SERVICE_TOKEN', async () => {
|
|
process.env.VK_SERVICE_TOKEN = secretToken;
|
|
const sessionStore = defaultSessionStore;
|
|
const sessionId = await sessionStore.createSession({ id: 'usr_sec_test', vkUserId: '12345' });
|
|
const req = new NextRequest('http://localhost/api/posts/preview', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
Cookie: `${SESSION_COOKIE_NAME}=${sessionId}`,
|
|
},
|
|
body: JSON.stringify({ url: 'https://vk.com/wall-1_1' }),
|
|
});
|
|
|
|
const res = await previewPost(req);
|
|
const text = await res.text();
|
|
expect(text).not.toContain(secretToken);
|
|
delete process.env.VK_SERVICE_TOKEN;
|
|
});
|
|
|
|
it('Created giveaway does not persist VK_SERVICE_TOKEN', async () => {
|
|
process.env.VK_SERVICE_TOKEN = secretToken;
|
|
const repo = new MemoryGiveawayRepository();
|
|
GiveawayStore.setRepository(repo);
|
|
|
|
const gw = await GiveawayStore.create({
|
|
sourceUrl: 'https://vk.com/wall-1_1',
|
|
post: {
|
|
platform: 'VK',
|
|
ownerId: '-1',
|
|
postId: '1',
|
|
sourceUrl: 'https://vk.com/wall-1_1',
|
|
title: 'Test',
|
|
text: 'Test',
|
|
likesCount: 0,
|
|
commentsCount: 0,
|
|
repostsCount: 0,
|
|
},
|
|
filterRules: {
|
|
requireLike: true,
|
|
requireComment: false,
|
|
requireRepost: false,
|
|
requireSubscription: false,
|
|
excludeAdmins: false,
|
|
excludeBlacklistedIds: [],
|
|
excludeDuplicateComments: true,
|
|
},
|
|
organizerId: 'usr_security_test',
|
|
});
|
|
|
|
const json = JSON.stringify(gw);
|
|
expect(json).not.toContain(secretToken);
|
|
delete process.env.VK_SERVICE_TOKEN;
|
|
});
|
|
|
|
it('VkProvider error messages do not contain the token', async () => {
|
|
const provider = new VkProvider(secretToken);
|
|
let thrownMessage = '';
|
|
try {
|
|
// Force error by calling with no token? Provider has token, but callApi will fail network.
|
|
await provider.fetchPost('not-a-valid-url');
|
|
} catch (err: any) {
|
|
thrownMessage = err.message || '';
|
|
}
|
|
expect(thrownMessage).not.toContain(secretToken);
|
|
});
|
|
|
|
it('.gitignore excludes local environment files', () => {
|
|
const gitignore = readFileSync(resolve(__dirname, '../.gitignore'), 'utf-8');
|
|
expect(gitignore).toContain('.env');
|
|
expect(gitignore).toContain('.env*.local');
|
|
});
|
|
|
|
it('.env.example does not contain real secrets', () => {
|
|
const envExample = readFileSync(resolve(__dirname, '../.env.example'), 'utf-8');
|
|
expect(envExample).toContain('your_vk_service_token_here');
|
|
expect(envExample).not.toMatch(/vk1\.[a-zA-Z0-9]/);
|
|
});
|
|
});
|