Give both configured Discord accounts owner access
All checks were successful
Testar e publicar imagem Docker / publish (push) Successful in 18s
All checks were successful
Testar e publicar imagem Docker / publish (push) Successful in 18s
This commit is contained in:
parent
e2c0e703ea
commit
8c430b9486
@ -1,5 +1,5 @@
|
|||||||
APP_URL=http://192.168.88.99:3080
|
APP_URL=http://192.168.88.99:3080
|
||||||
DISCORD_CLIENT_ID=
|
DISCORD_CLIENT_ID=
|
||||||
DISCORD_CLIENT_SECRET=
|
DISCORD_CLIENT_SECRET=
|
||||||
OWNER_DISCORD_ID=376768118535749633
|
OWNER_DISCORD_ID=376768118535749633,723613831196311603
|
||||||
ALLOWED_DISCORD_IDS=723613831196311603
|
ALLOWED_DISCORD_IDS=
|
||||||
|
|||||||
@ -27,8 +27,8 @@ Variáveis de ambiente:
|
|||||||
- `APP_URL`: endereço do navegador, por exemplo `http://192.168.88.99:3080`.
|
- `APP_URL`: endereço do navegador, por exemplo `http://192.168.88.99:3080`.
|
||||||
- `DISCORD_CLIENT_ID`: ID da aplicação Discord.
|
- `DISCORD_CLIENT_ID`: ID da aplicação Discord.
|
||||||
- `DISCORD_CLIENT_SECRET`: segredo OAuth da aplicação.
|
- `DISCORD_CLIENT_SECRET`: segredo OAuth da aplicação.
|
||||||
- `OWNER_DISCORD_ID`: ID numérico da conta do dono.
|
- `OWNER_DISCORD_ID`: IDs numéricos dos donos, separados por vírgulas. A stack inclui `376768118535749633,723613831196311603`. Um único ID continua a ser aceite.
|
||||||
- `ALLOWED_DISCORD_IDS`: IDs de leitores separados por vírgulas. Por defeito, `723613831196311603`; valor vazio remove todos os leitores.
|
- `ALLOWED_DISCORD_IDS`: IDs de leitores separados por vírgulas. Por defeito, não há leitores. Um ID presente nas duas listas recebe o perfil Dono.
|
||||||
|
|
||||||
No Discord Developer Portal, adicionar o redirect exato `http://192.168.88.99:3080/auth/discord/callback`, ajustando ao `APP_URL`. O navegador e o container precisam de Internet para o OAuth; o site pode continuar apenas na rede local.
|
No Discord Developer Portal, adicionar o redirect exato `http://192.168.88.99:3080/auth/discord/callback`, ajustando ao `APP_URL`. O navegador e o container precisam de Internet para o OAuth; o site pode continuar apenas na rede local.
|
||||||
|
|
||||||
|
|||||||
@ -5,11 +5,11 @@ import { tmpdir } from 'node:os';
|
|||||||
import { join } from 'node:path';
|
import { join } from 'node:path';
|
||||||
import { createApp } from './server.mjs';
|
import { createApp } from './server.mjs';
|
||||||
|
|
||||||
test('OAuth rejects strangers, invalid state and replay; owner session requires authentication', async () => {
|
test('OAuth rejects strangers, invalid state and replay; both owners can write and readers cannot', async () => {
|
||||||
const directory = mkdtempSync(join(tmpdir(), 'botflow-test-'));
|
const directory = mkdtempSync(join(tmpdir(), 'botflow-test-'));
|
||||||
let profileId = '999999999999999999';
|
let profileId = '999999999999999999';
|
||||||
let requests = 0;
|
let requests = 0;
|
||||||
const server = createApp({ env: { DATA_DIR: directory, APP_URL: 'http://localhost:3080', DISCORD_CLIENT_ID: 'client', DISCORD_CLIENT_SECRET: 'secret', OWNER_DISCORD_ID: '111111111111111111' }, discordFetch: async (url) => {
|
const server = createApp({ env: { DATA_DIR: directory, APP_URL: 'http://localhost:3080', DISCORD_CLIENT_ID: 'client', DISCORD_CLIENT_SECRET: 'secret', OWNER_DISCORD_ID: '376768118535749633, 723613831196311603', ALLOWED_DISCORD_IDS: '222222222222222222,723613831196311603' }, discordFetch: async (url) => {
|
||||||
requests++;
|
requests++;
|
||||||
return { ok: true, json: async () => url.endsWith('/token') ? { access_token: 'temporary' } : { id: profileId, username: 'bxio' } };
|
return { ok: true, json: async () => url.endsWith('/token') ? { access_token: 'temporary' } : { id: profileId, username: 'bxio' } };
|
||||||
} });
|
} });
|
||||||
@ -25,7 +25,7 @@ test('OAuth rejects strangers, invalid state and replay; owner session requires
|
|||||||
const outsider = await begin();
|
const outsider = await begin();
|
||||||
const denied = await request(`/auth/discord/callback?state=${outsider.state}&code=code`, { headers: { Cookie: outsider.cookie } });
|
const denied = await request(`/auth/discord/callback?state=${outsider.state}&code=code`, { headers: { Cookie: outsider.cookie } });
|
||||||
assert.equal(denied.headers.get('location'), '/?error=denied');
|
assert.equal(denied.headers.get('location'), '/?error=denied');
|
||||||
profileId = '723613831196311603';
|
profileId = '222222222222222222';
|
||||||
const reader = await begin();
|
const reader = await begin();
|
||||||
const readerLogin = await request(`/auth/discord/callback?state=${reader.state}&code=code`, { headers: { Cookie: reader.cookie } });
|
const readerLogin = await request(`/auth/discord/callback?state=${reader.state}&code=code`, { headers: { Cookie: reader.cookie } });
|
||||||
assert.equal(readerLogin.headers.get('location'), '/');
|
assert.equal(readerLogin.headers.get('location'), '/');
|
||||||
@ -33,7 +33,15 @@ test('OAuth rejects strangers, invalid state and replay; owner session requires
|
|||||||
const readerProfile = await (await request('/api/me', { headers: { Cookie: readerCookie } })).json();
|
const readerProfile = await (await request('/api/me', { headers: { Cookie: readerCookie } })).json();
|
||||||
assert.equal(readerProfile.user.role, 'Leitor');
|
assert.equal(readerProfile.user.role, 'Leitor');
|
||||||
assert.equal((await request('/api/dashboard', { headers: { Cookie: readerCookie } })).status, 200);
|
assert.equal((await request('/api/dashboard', { headers: { Cookie: readerCookie } })).status, 200);
|
||||||
profileId = '111111111111111111';
|
assert.equal((await request('/api/bots', { method: 'POST', headers: { Cookie: readerCookie, Origin: 'http://localhost:3080', 'Content-Type': 'application/json' }, body: JSON.stringify({name:'Reader bot'}) })).status, 403);
|
||||||
|
profileId = '723613831196311603';
|
||||||
|
const secondOwner = await begin();
|
||||||
|
const secondLogin = await request(`/auth/discord/callback?state=${secondOwner.state}&code=code`, { headers: { Cookie: secondOwner.cookie } });
|
||||||
|
assert.equal(secondLogin.headers.get('location'), '/');
|
||||||
|
const secondCookie = secondLogin.headers.getSetCookie().find(v => v.startsWith('session=')).split(';')[0];
|
||||||
|
assert.equal((await (await request('/api/me', { headers: { Cookie: secondCookie } })).json()).user.role, 'Dono');
|
||||||
|
assert.equal((await request('/api/bots', { method: 'POST', headers: { Cookie: secondCookie, Origin: 'http://localhost:3080', 'Content-Type': 'application/json' }, body: JSON.stringify({name:'Second owner bot'}) })).status, 201);
|
||||||
|
profileId = '376768118535749633';
|
||||||
const owner = await begin();
|
const owner = await begin();
|
||||||
const callback = `/auth/discord/callback?state=${owner.state}&code=code`;
|
const callback = `/auth/discord/callback?state=${owner.state}&code=code`;
|
||||||
const login = await request(callback, { headers: { Cookie: owner.cookie } });
|
const login = await request(callback, { headers: { Cookie: owner.cookie } });
|
||||||
@ -41,6 +49,7 @@ test('OAuth rejects strangers, invalid state and replay; owner session requires
|
|||||||
const sessionCookie = login.headers.getSetCookie().find(v => v.startsWith('session=')).split(';')[0];
|
const sessionCookie = login.headers.getSetCookie().find(v => v.startsWith('session=')).split(';')[0];
|
||||||
assert.equal((await (await request('/api/me', { headers: { Cookie: sessionCookie } })).json()).user.role, 'Dono');
|
assert.equal((await (await request('/api/me', { headers: { Cookie: sessionCookie } })).json()).user.role, 'Dono');
|
||||||
assert.equal((await request('/api/dashboard', { headers: { Cookie: sessionCookie } })).status, 200);
|
assert.equal((await request('/api/dashboard', { headers: { Cookie: sessionCookie } })).status, 200);
|
||||||
|
assert.equal((await request('/api/bots', { method: 'POST', headers: { Cookie: sessionCookie, Origin: 'http://localhost:3080', 'Content-Type': 'application/json' }, body: JSON.stringify({name:'First owner bot'}) })).status, 201);
|
||||||
assert.equal((await request(callback, { headers: { Cookie: owner.cookie } })).headers.get('location'), '/?error=authentication');
|
assert.equal((await request(callback, { headers: { Cookie: owner.cookie } })).headers.get('location'), '/?error=authentication');
|
||||||
assert.equal((await request('/auth/logout', { method: 'POST', headers: { Cookie: sessionCookie, Origin: 'http://evil.example' } })).status, 403);
|
assert.equal((await request('/auth/logout', { method: 'POST', headers: { Cookie: sessionCookie, Origin: 'http://evil.example' } })).status, 403);
|
||||||
assert.equal((await request('/auth/logout', { method: 'POST', headers: { Cookie: sessionCookie, Origin: 'http://localhost:3080' } })).status, 200);
|
assert.equal((await request('/auth/logout', { method: 'POST', headers: { Cookie: sessionCookie, Origin: 'http://localhost:3080' } })).status, 200);
|
||||||
|
|||||||
@ -7,7 +7,8 @@ services:
|
|||||||
APP_URL: ${APP_URL:-http://192.168.88.99:3080}
|
APP_URL: ${APP_URL:-http://192.168.88.99:3080}
|
||||||
DISCORD_CLIENT_ID: ${DISCORD_CLIENT_ID:?Define DISCORD_CLIENT_ID}
|
DISCORD_CLIENT_ID: ${DISCORD_CLIENT_ID:?Define DISCORD_CLIENT_ID}
|
||||||
DISCORD_CLIENT_SECRET: ${DISCORD_CLIENT_SECRET:?Define DISCORD_CLIENT_SECRET}
|
DISCORD_CLIENT_SECRET: ${DISCORD_CLIENT_SECRET:?Define DISCORD_CLIENT_SECRET}
|
||||||
OWNER_DISCORD_ID: ${OWNER_DISCORD_ID:-376768118535749633}
|
OWNER_DISCORD_ID: ${OWNER_DISCORD_ID:-376768118535749633,723613831196311603}
|
||||||
|
ALLOWED_DISCORD_IDS: ${ALLOWED_DISCORD_IDS:-}
|
||||||
volumes:
|
volumes:
|
||||||
- botflow-data:/app/data
|
- botflow-data:/app/data
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|||||||
@ -12,7 +12,7 @@ services:
|
|||||||
APP_URL: http://192.168.88.99:3080
|
APP_URL: http://192.168.88.99:3080
|
||||||
DISCORD_CLIENT_ID: "477999574804594708"
|
DISCORD_CLIENT_ID: "477999574804594708"
|
||||||
DISCORD_CLIENT_SECRET: ${DISCORD_CLIENT_SECRET:?Define DISCORD_CLIENT_SECRET}
|
DISCORD_CLIENT_SECRET: ${DISCORD_CLIENT_SECRET:?Define DISCORD_CLIENT_SECRET}
|
||||||
OWNER_DISCORD_ID: "376768118535749633"
|
OWNER_DISCORD_ID: "376768118535749633,723613831196311603"
|
||||||
volumes:
|
volumes:
|
||||||
- ts6_botflow_data:/app/data
|
- ts6_botflow_data:/app/data
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|||||||
@ -15,10 +15,12 @@ export function createApp({ env = process.env, discordFetch = fetch } = {}) {
|
|||||||
const studio = createStudio(db, dataDir);
|
const studio = createStudio(db, dataDir);
|
||||||
const pending = new Map();
|
const pending = new Map();
|
||||||
const root = fileURLToPath(new URL('./public/', import.meta.url));
|
const root = fileURLToPath(new URL('./public/', import.meta.url));
|
||||||
const configured = Boolean(env.DISCORD_CLIENT_ID && env.DISCORD_CLIENT_SECRET && /^\d{17,20}$/.test(env.OWNER_DISCORD_ID || ''));
|
const ids = value => (value || '').split(',').map(id => id.trim()).filter(id => /^\d{17,20}$/.test(id));
|
||||||
|
const owners = ids(env.OWNER_DISCORD_ID);
|
||||||
|
const configured = Boolean(env.DISCORD_CLIENT_ID && env.DISCORD_CLIENT_SECRET && owners.length);
|
||||||
const roleFor = id => {
|
const roleFor = id => {
|
||||||
if (id === env.OWNER_DISCORD_ID) return 'Dono';
|
if (owners.includes(id)) return 'Dono';
|
||||||
const allowed = (env.ALLOWED_DISCORD_IDS ?? '723613831196311603').split(',').map(value => value.trim()).filter(value => /^\d{17,20}$/.test(value));
|
const allowed = ids(env.ALLOWED_DISCORD_IDS ?? '');
|
||||||
return allowed.includes(id) ? 'Leitor' : null;
|
return allowed.includes(id) ? 'Leitor' : null;
|
||||||
};
|
};
|
||||||
const token = () => randomBytes(32).toString('hex');
|
const token = () => randomBytes(32).toString('hex');
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user