diff --git a/.env.example b/.env.example index cb15442..6489a18 100644 --- a/.env.example +++ b/.env.example @@ -2,3 +2,4 @@ APP_URL=http://192.168.88.99:3080 DISCORD_CLIENT_ID= DISCORD_CLIENT_SECRET= OWNER_DISCORD_ID=376768118535749633 +ALLOWED_DISCORD_IDS=723613831196311603 diff --git a/README.md b/README.md index e2e0fea..c3c3f32 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ No Discord Developer Portal, OAuth2 → Redirects, adicionar exatamente `http:// Para copiar o ID: Discord → Definições → Avançadas → Modo de programador; clicar com o botão direito na tua conta → Copiar ID do utilizador. -O navegador e o container precisam de Internet para o OAuth Discord. O painel não precisa de estar publicado na Internet. A rede local precisa de conseguir aceder à porta 3080. Uma conta diferente do OWNER_DISCORD_ID é recusada. Esta versão não tem gestão de outros perfis. +O navegador e o container precisam de Internet para o OAuth Discord. O painel não precisa de estar publicado na Internet. A rede local precisa de conseguir aceder à porta 3080. A conta OWNER_DISCORD_ID entra como Dono. O ID 723613831196311603 também está autorizado como Leitor por defeito. ALLOWED_DISCORD_IDS permite substituir a lista de leitores (IDs separados por vírgulas; valor vazio remove todos). Outras contas são recusadas. A gestão de utilizadores pelo painel ainda não está implementada. Local: Node 22.15+, definir as mesmas variáveis e executar `npm start`. Testes: `npm test`. diff --git a/auth.test.mjs b/auth.test.mjs index fd87506..f09146f 100644 --- a/auth.test.mjs +++ b/auth.test.mjs @@ -25,12 +25,21 @@ test('OAuth rejects strangers, invalid state and replay; owner session requires const outsider = await begin(); const denied = await request(`/auth/discord/callback?state=${outsider.state}&code=code`, { headers: { Cookie: outsider.cookie } }); assert.equal(denied.headers.get('location'), '/?error=denied'); + profileId = '723613831196311603'; + const reader = await begin(); + const readerLogin = await request(`/auth/discord/callback?state=${reader.state}&code=code`, { headers: { Cookie: reader.cookie } }); + assert.equal(readerLogin.headers.get('location'), '/'); + const readerCookie = readerLogin.headers.getSetCookie().find(v => v.startsWith('session=')).split(';')[0]; + const readerProfile = await (await request('/api/me', { headers: { Cookie: readerCookie } })).json(); + assert.equal(readerProfile.user.role, 'Leitor'); + assert.equal((await request('/api/dashboard', { headers: { Cookie: readerCookie } })).status, 200); profileId = '111111111111111111'; const owner = await begin(); const callback = `/auth/discord/callback?state=${owner.state}&code=code`; const login = await request(callback, { headers: { Cookie: owner.cookie } }); assert.equal(login.headers.get('location'), '/'); 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 request('/api/dashboard', { headers: { Cookie: sessionCookie } })).status, 200); 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); diff --git a/public/app.js b/public/app.js index 61c4537..2603f00 100644 --- a/public/app.js +++ b/public/app.js @@ -1,5 +1,5 @@ const app = document.querySelector('#app'); -const errors = { denied: 'Esta conta ainda não tem acesso. Nesta fase, só o dono pode entrar.', authentication: 'O login expirou ou foi cancelado. Tenta novamente.', configuration: 'Falta configurar o Discord e o ID numérico do dono no Docker.' }; +const errors = { denied: 'Esta conta não está autorizada a entrar no painel.', authentication: 'O login expirou ou foi cancelado. Tenta novamente.', configuration: 'Falta configurar o Discord e o ID numérico do dono no Docker.' }; async function boot() { const response = await fetch('/api/me'); if (!response.ok) throw new Error(); @@ -18,11 +18,13 @@ async function boot() { function page(name) { document.querySelectorAll('.nav').forEach(b => b.classList.toggle('active', b.dataset.page === name)); if (name === 'access') { - content.innerHTML = '
A tua conta está autorizada como dono.
A tua conta está autorizada a entrar no painel.
A primeira experiência valida o painel e o acesso.
O editor de flows e a ligação ao TeamSpeak serão a próxima etapa.
Bem-vindo ao teu centro de automações.
A atividade dos bots aparecerá aqui.
Começamos por testar o login. Depois ligamos os bots e construímos os flows.
Bem-vindo ao teu centro de automações.
A atividade dos bots aparecerá aqui.
Começamos por testar o login. Depois ligamos os bots e construímos os flows.