Migrate [Discord] implementation to use bot token (#5346)
* Migrate [Discord] implementation to use bot token
* Rework authorization field creation
* Revert "Rework authorization field creation"
This reverts commit caf65bde5d.
* Add LGTM exclusion for hardcoded credentials
This commit is contained in:
@@ -64,8 +64,6 @@ public:
|
||||
|
||||
fetchLimit: 'FETCH_LIMIT'
|
||||
|
||||
shieldsProductionHerokuHacks: 'SHIELDS_PRODUCTION_HEROKU_HACKS'
|
||||
|
||||
private:
|
||||
azure_devops_token: 'AZURE_DEVOPS_TOKEN'
|
||||
bintray_user: 'BINTRAY_USER'
|
||||
@@ -74,6 +72,7 @@ private:
|
||||
bitbucket_password: 'BITBUCKET_PASS'
|
||||
bitbucket_server_username: 'BITBUCKET_SERVER_USER'
|
||||
bitbucket_server_password: 'BITBUCKET_SERVER_PASS'
|
||||
discord_bot_token: 'DISCORD_BOT_TOKEN'
|
||||
drone_token: 'DRONE_TOKEN'
|
||||
gh_client_id: 'GH_CLIENT_ID'
|
||||
gh_client_secret: 'GH_CLIENT_SECRET'
|
||||
|
||||
@@ -36,6 +36,4 @@ public:
|
||||
|
||||
fetchLimit: '10MB'
|
||||
|
||||
shieldsProductionHerokuHacks: false
|
||||
|
||||
private: {}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
private:
|
||||
# These are the keys which are set on the production servers.
|
||||
discord_bot_token: ...
|
||||
gh_client_id: ...
|
||||
gh_client_secret: ...
|
||||
redis_url: ...
|
||||
|
||||
@@ -146,9 +146,11 @@ class AuthHelper {
|
||||
)
|
||||
}
|
||||
|
||||
get _bearerAuthHeader() {
|
||||
_bearerAuthHeader(bearerKey) {
|
||||
const { _pass: pass } = this
|
||||
return this.isConfigured ? { Authorization: `Bearer ${pass}` } : undefined
|
||||
return this.isConfigured
|
||||
? { Authorization: `${bearerKey} ${pass}` }
|
||||
: undefined
|
||||
}
|
||||
|
||||
static _mergeHeaders(requestParams, headers) {
|
||||
@@ -168,9 +170,15 @@ class AuthHelper {
|
||||
}
|
||||
}
|
||||
|
||||
withBearerAuthHeader(requestParams) {
|
||||
withBearerAuthHeader(
|
||||
requestParams,
|
||||
bearerKey = 'Bearer' // lgtm [js/hardcoded-credentials]
|
||||
) {
|
||||
return this._withAnyAuth(requestParams, requestParams =>
|
||||
this.constructor._mergeHeaders(requestParams, this._bearerAuthHeader)
|
||||
this.constructor._mergeHeaders(
|
||||
requestParams,
|
||||
this._bearerAuthHeader(bearerKey)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -148,13 +148,13 @@ const publicConfigSchema = Joi.object({
|
||||
rateLimit: Joi.boolean().required(),
|
||||
handleInternalErrors: Joi.boolean().required(),
|
||||
fetchLimit: Joi.string().regex(/^[0-9]+(b|kb|mb|gb|tb)$/i),
|
||||
shieldsProductionHerokuHacks: Joi.boolean(),
|
||||
}).required()
|
||||
|
||||
const privateConfigSchema = Joi.object({
|
||||
azure_devops_token: Joi.string(),
|
||||
bintray_user: Joi.string(),
|
||||
bintray_apikey: Joi.string(),
|
||||
discord_bot_token: Joi.string(),
|
||||
drone_token: Joi.string(),
|
||||
gh_client_id: Joi.string(),
|
||||
gh_client_secret: Joi.string(),
|
||||
@@ -392,8 +392,6 @@ class Server {
|
||||
rasterUrl: config.public.rasterUrl,
|
||||
private: config.private,
|
||||
public: config.public,
|
||||
shieldsProductionHerokuHacks:
|
||||
config.public.shieldsProductionHerokuHacks,
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
@@ -34,6 +34,7 @@ Production hosting is managed by the Shields ops team:
|
||||
| Cloudflare (CDN) | Access management | @espadrine |
|
||||
| Cloudflare (CDN) | Admin access | @calebcartwright, @chris48s, @espadrine, @paulmelnikow, @PyvesB |
|
||||
| Twitch | OAuth app | @PyvesB |
|
||||
| Discord | OAuth app | @PyvesB |
|
||||
| YouTube | Account owner | @PyvesB |
|
||||
| OpenStreetMap (for Wheelmap) | Account owner | @paulmelnikow |
|
||||
| DNS | Account owner | @olivierlacan |
|
||||
|
||||
@@ -105,6 +105,15 @@ self-hosted Shields installation access to private repositories hosted on bitbuc
|
||||
Bitbucket badges use basic auth. Provide a username and password to give your
|
||||
self-hosted Shields installation access to a private Bitbucket Server instance.
|
||||
|
||||
### Discord
|
||||
|
||||
Using a token for Dicsord is optional but will allow higher API rates.
|
||||
|
||||
- `DISCORD_BOT_TOKEN` (yml: `discord_bot_token`)
|
||||
|
||||
Register an application in the [Discord developer console](https://discord.com/developers).
|
||||
To obtain a token, simply create a bot for your application.
|
||||
|
||||
### Drone
|
||||
|
||||
- `DRONE_ORIGINS` (yml: `public.services.drone.authorizedOrigins`)
|
||||
|
||||
@@ -4,15 +4,10 @@ const Joi = require('@hapi/joi')
|
||||
const { nonNegativeInteger } = require('../validators')
|
||||
const { BaseJsonService } = require('..')
|
||||
|
||||
const discordSchema = Joi.object({
|
||||
const schema = Joi.object({
|
||||
presence_count: nonNegativeInteger,
|
||||
}).required()
|
||||
|
||||
const proxySchema = Joi.object({
|
||||
message: Joi.string().required(),
|
||||
color: Joi.string().required(),
|
||||
}).required()
|
||||
|
||||
const documentation = `
|
||||
<p>
|
||||
The Discord badge requires the <code>SERVER ID</code> in order access the Discord JSON API.
|
||||
@@ -41,6 +36,14 @@ module.exports = class Discord extends BaseJsonService {
|
||||
}
|
||||
}
|
||||
|
||||
static get auth() {
|
||||
return {
|
||||
passKey: 'discord_bot_token',
|
||||
authorizedOrigins: ['https://discord.com'],
|
||||
isRequired: false,
|
||||
}
|
||||
}
|
||||
|
||||
static get examples() {
|
||||
return [
|
||||
{
|
||||
@@ -67,36 +70,24 @@ module.exports = class Discord extends BaseJsonService {
|
||||
}
|
||||
}
|
||||
|
||||
constructor(context, config) {
|
||||
super(context, config)
|
||||
this._shieldsProductionHerokuHacks = config.shieldsProductionHerokuHacks
|
||||
}
|
||||
|
||||
async fetch({ serverId }) {
|
||||
const url = `https://discord.com/api/guilds/${serverId}/widget.json`
|
||||
return this._requestJson({
|
||||
url,
|
||||
schema: discordSchema,
|
||||
errorMessages: {
|
||||
404: 'invalid server',
|
||||
403: 'widget disabled',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async fetchOvhProxy({ serverId }) {
|
||||
return this._requestJson({
|
||||
url: `https://legacy-img.shields.io/discord/${serverId}.json`,
|
||||
schema: proxySchema,
|
||||
})
|
||||
const url = `https://discord.com/api/v6/guilds/${serverId}/widget.json`
|
||||
return this._requestJson(
|
||||
this.authHelper.withBearerAuthHeader(
|
||||
{
|
||||
url,
|
||||
schema,
|
||||
errorMessages: {
|
||||
404: 'invalid server',
|
||||
403: 'widget disabled',
|
||||
},
|
||||
},
|
||||
'Bot'
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
async handle({ serverId }) {
|
||||
if (this._shieldsProductionHerokuHacks) {
|
||||
const { message, color } = await this.fetchOvhProxy({ serverId })
|
||||
return { message, color }
|
||||
}
|
||||
|
||||
const data = await this.fetch({ serverId })
|
||||
return this.constructor.render({ members: data.presence_count })
|
||||
}
|
||||
|
||||
40
services/discord/discord.spec.js
Normal file
40
services/discord/discord.spec.js
Normal file
@@ -0,0 +1,40 @@
|
||||
'use strict'
|
||||
|
||||
const { expect } = require('chai')
|
||||
const nock = require('nock')
|
||||
const { cleanUpNockAfterEach, defaultContext } = require('../test-helpers')
|
||||
const Discord = require('./discord.service')
|
||||
|
||||
describe('Discord', function () {
|
||||
cleanUpNockAfterEach()
|
||||
|
||||
it('sends the auth information as configured', async function () {
|
||||
const pass = 'password'
|
||||
const config = {
|
||||
private: {
|
||||
discord_bot_token: pass,
|
||||
},
|
||||
}
|
||||
|
||||
const scope = nock(`https://discord.com`, {
|
||||
// This ensures that the expected credential is actually being sent with the HTTP request.
|
||||
// Without this the request wouldn't match and the test would fail.
|
||||
reqheaders: { Authorization: `Bot password` },
|
||||
})
|
||||
.get(`/api/v6/guilds/12345/widget.json`)
|
||||
.reply(200, {
|
||||
presence_count: 125,
|
||||
})
|
||||
|
||||
expect(
|
||||
await Discord.invoke(defaultContext, config, {
|
||||
serverId: '12345',
|
||||
})
|
||||
).to.deep.equal({
|
||||
message: '125 online',
|
||||
color: 'brightgreen',
|
||||
})
|
||||
|
||||
scope.done()
|
||||
})
|
||||
})
|
||||
@@ -19,7 +19,7 @@ t.create('widget disabled')
|
||||
.get('/12345.json')
|
||||
.intercept(nock =>
|
||||
nock('https://discord.com/')
|
||||
.get('/api/guilds/12345/widget.json')
|
||||
.get('/api/v6/guilds/12345/widget.json')
|
||||
.reply(403, {
|
||||
code: 50004,
|
||||
message: 'Widget Disabled',
|
||||
@@ -31,7 +31,7 @@ t.create('server error')
|
||||
.get('/12345.json')
|
||||
.intercept(nock =>
|
||||
nock('https://discord.com/')
|
||||
.get('/api/guilds/12345/widget.json')
|
||||
.get('/api/v6/guilds/12345/widget.json')
|
||||
.reply(500, 'Something broke')
|
||||
)
|
||||
.expectBadge({ label: 'chat', message: 'inaccessible' })
|
||||
|
||||
Reference in New Issue
Block a user