Add badges for [Keybase] (#2969)
* Add Keybase PGP badge * Return 'not found' if the key is not present * Change the default colour * Add more constraints to the schema * Render 64-bit fingerprints * Add example * Add a 'hex()' constraint to the fingerprint * Improve error handling * Add class 'KeybaseProfile' * Add unit tests for Keybase PGP * Add Keybase BTC * Add unit tests for Keybase BTC * Add Keybase ZEC * Add unit tests for Keybase ZEC * Add Keybase XLM * Add unit tests for Keybase XLM * Validate the BTC address using a regex Regex taken from https://mokagio.github.io/tech-journal/2014/11/21/regex-bitcoin.html. * Exclude 'not found' from addresses' value in unit tests * Remove useless keywords * Add the link to the Keybase API documentation * Move the colour into 'defaultBadgeData' * Remove the HTTP method 'GET' is already the default one. * Improve the error handling for Keybase BTC * Add more constraints to the Keybase BTC schema * Update one unit test for Keybase BTC * Fix the error handling for Keybase BTC * Add more unit tests for Keybase BTC * Improve the error handling for Keybase ZEC * Improve the error handling for Keybase PGP * Improve the error handling for Keybase XLM * Display a real username value in the examples * Include the status code in the schemas * Move the category to the base class The same category is used by all badges. * Add function 'transform' to the base class The function 'transform' is used to encapsulate the error handling logic as it is the same in each service.
This commit is contained in:
94
services/keybase/keybase-btc.service.js
Normal file
94
services/keybase/keybase-btc.service.js
Normal file
@@ -0,0 +1,94 @@
|
||||
'use strict'
|
||||
|
||||
const KeybaseProfile = require('./keybase-profile')
|
||||
const Joi = require('joi')
|
||||
const { nonNegativeInteger } = require('../validators')
|
||||
|
||||
const bitcoinAddressSchema = Joi.object({
|
||||
status: Joi.object({
|
||||
code: nonNegativeInteger.required(),
|
||||
}).required(),
|
||||
them: Joi.array()
|
||||
.items(
|
||||
Joi.object({
|
||||
cryptocurrency_addresses: Joi.object({
|
||||
bitcoin: Joi.array().items(
|
||||
Joi.object({
|
||||
address: Joi.string().required(),
|
||||
}).required()
|
||||
),
|
||||
})
|
||||
.required()
|
||||
.allow(null),
|
||||
})
|
||||
.required()
|
||||
.allow(null)
|
||||
)
|
||||
.min(0)
|
||||
.max(1),
|
||||
}).required()
|
||||
|
||||
module.exports = class KeybaseBTC extends KeybaseProfile {
|
||||
static get apiVersion() {
|
||||
return '1.0'
|
||||
}
|
||||
|
||||
static get route() {
|
||||
return {
|
||||
base: 'keybase/btc',
|
||||
pattern: ':username',
|
||||
}
|
||||
}
|
||||
|
||||
static get defaultBadgeData() {
|
||||
return {
|
||||
label: 'btc',
|
||||
color: 'informational',
|
||||
}
|
||||
}
|
||||
|
||||
async handle({ username }) {
|
||||
const options = {
|
||||
form: {
|
||||
usernames: username,
|
||||
fields: 'cryptocurrency_addresses',
|
||||
},
|
||||
}
|
||||
|
||||
const data = await this.fetch({
|
||||
schema: bitcoinAddressSchema,
|
||||
options,
|
||||
})
|
||||
|
||||
const { user } = this.transform({ data })
|
||||
const bitcoinAddresses = user.cryptocurrency_addresses.bitcoin
|
||||
|
||||
if (bitcoinAddresses == null || bitcoinAddresses.length === 0) {
|
||||
return {
|
||||
message: 'no bitcoin addresses found',
|
||||
color: 'inactive',
|
||||
}
|
||||
}
|
||||
|
||||
return this.constructor.render({ address: bitcoinAddresses[0].address })
|
||||
}
|
||||
|
||||
static render({ address }) {
|
||||
return {
|
||||
message: address,
|
||||
}
|
||||
}
|
||||
|
||||
static get examples() {
|
||||
return [
|
||||
{
|
||||
title: 'Keybase BTC',
|
||||
namedParams: { username: 'skyplabs' },
|
||||
staticPreview: this.render({
|
||||
address: '12ufRLmbEmgjsdGzhUUFY4pcfiQZyRPV9J',
|
||||
}),
|
||||
keywords: ['bitcoin'],
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
42
services/keybase/keybase-btc.tester.js
Normal file
42
services/keybase/keybase-btc.tester.js
Normal file
@@ -0,0 +1,42 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('joi')
|
||||
const { withRegex } = require('../test-validators')
|
||||
|
||||
const t = (module.exports = require('../tester').createServiceTester())
|
||||
|
||||
t.create('existing bitcoin address')
|
||||
.get('/skyplabs.json')
|
||||
.expectJSONTypes(
|
||||
Joi.object({
|
||||
name: 'btc',
|
||||
value: withRegex(/^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$/),
|
||||
})
|
||||
)
|
||||
|
||||
t.create('unknown username')
|
||||
.get('/skyplabsssssss.json')
|
||||
.expectJSONTypes(
|
||||
Joi.object({
|
||||
name: 'btc',
|
||||
value: 'profile not found',
|
||||
})
|
||||
)
|
||||
|
||||
t.create('invalid username')
|
||||
.get('/s.json')
|
||||
.expectJSONTypes(
|
||||
Joi.object({
|
||||
name: 'btc',
|
||||
value: 'invalid username',
|
||||
})
|
||||
)
|
||||
|
||||
t.create('missing bitcoin address')
|
||||
.get('/test.json')
|
||||
.expectJSONTypes(
|
||||
Joi.object({
|
||||
name: 'btc',
|
||||
value: 'no bitcoin addresses found',
|
||||
})
|
||||
)
|
||||
89
services/keybase/keybase-pgp.service.js
Normal file
89
services/keybase/keybase-pgp.service.js
Normal file
@@ -0,0 +1,89 @@
|
||||
'use strict'
|
||||
|
||||
const KeybaseProfile = require('./keybase-profile')
|
||||
const Joi = require('joi')
|
||||
const { nonNegativeInteger } = require('../validators')
|
||||
|
||||
const keyFingerprintSchema = Joi.object({
|
||||
status: Joi.object({
|
||||
code: nonNegativeInteger.required(),
|
||||
}).required(),
|
||||
them: Joi.array()
|
||||
.items(
|
||||
Joi.object({
|
||||
public_keys: {
|
||||
primary: {
|
||||
key_fingerprint: Joi.string()
|
||||
.hex()
|
||||
.required(),
|
||||
},
|
||||
},
|
||||
})
|
||||
.required()
|
||||
.allow(null)
|
||||
)
|
||||
.min(0)
|
||||
.max(1),
|
||||
}).required()
|
||||
|
||||
module.exports = class KeybasePGP extends KeybaseProfile {
|
||||
static get apiVersion() {
|
||||
return '1.0'
|
||||
}
|
||||
|
||||
static get route() {
|
||||
return {
|
||||
base: 'keybase/pgp',
|
||||
pattern: ':username',
|
||||
}
|
||||
}
|
||||
|
||||
static get defaultBadgeData() {
|
||||
return {
|
||||
label: 'pgp',
|
||||
color: 'informational',
|
||||
}
|
||||
}
|
||||
|
||||
async handle({ username }) {
|
||||
const options = {
|
||||
form: {
|
||||
usernames: username,
|
||||
fields: 'public_keys',
|
||||
},
|
||||
}
|
||||
|
||||
const data = await this.fetch({
|
||||
schema: keyFingerprintSchema,
|
||||
options,
|
||||
})
|
||||
|
||||
const { user } = this.transform({ data })
|
||||
const primaryKey = user.public_keys.primary
|
||||
|
||||
if (primaryKey == null) {
|
||||
return {
|
||||
message: 'no key fingerprint found',
|
||||
color: 'inactive',
|
||||
}
|
||||
}
|
||||
|
||||
return this.constructor.render({ fingerprint: primaryKey.key_fingerprint })
|
||||
}
|
||||
|
||||
static render({ fingerprint }) {
|
||||
return {
|
||||
message: fingerprint.slice(-16).toUpperCase(),
|
||||
}
|
||||
}
|
||||
|
||||
static get examples() {
|
||||
return [
|
||||
{
|
||||
title: 'Keybase PGP',
|
||||
namedParams: { username: 'skyplabs' },
|
||||
staticPreview: this.render({ fingerprint: '1863145FD39EE07E' }),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
43
services/keybase/keybase-pgp.tester.js
Normal file
43
services/keybase/keybase-pgp.tester.js
Normal file
@@ -0,0 +1,43 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('joi')
|
||||
|
||||
const t = (module.exports = require('../tester').createServiceTester())
|
||||
|
||||
t.create('existing key fingerprint')
|
||||
.get('/skyplabs.json')
|
||||
.expectJSONTypes(
|
||||
Joi.object({
|
||||
name: 'pgp',
|
||||
value: Joi.string()
|
||||
.hex()
|
||||
.length(16),
|
||||
})
|
||||
)
|
||||
|
||||
t.create('unknown username')
|
||||
.get('/skyplabsssssss.json')
|
||||
.expectJSONTypes(
|
||||
Joi.object({
|
||||
name: 'pgp',
|
||||
value: 'profile not found',
|
||||
})
|
||||
)
|
||||
|
||||
t.create('invalid username')
|
||||
.get('/s.json')
|
||||
.expectJSONTypes(
|
||||
Joi.object({
|
||||
name: 'pgp',
|
||||
value: 'invalid username',
|
||||
})
|
||||
)
|
||||
|
||||
t.create('missing key fingerprint')
|
||||
.get('/skyp.json')
|
||||
.expectJSONTypes(
|
||||
Joi.object({
|
||||
name: 'pgp',
|
||||
value: 'no key fingerprint found',
|
||||
})
|
||||
)
|
||||
38
services/keybase/keybase-profile.js
Normal file
38
services/keybase/keybase-profile.js
Normal file
@@ -0,0 +1,38 @@
|
||||
'use strict'
|
||||
|
||||
const { BaseJsonService } = require('..')
|
||||
const { NotFound } = require('..')
|
||||
|
||||
module.exports = class KeybaseProfile extends BaseJsonService {
|
||||
static get apiVersion() {
|
||||
throw new Error(`apiVersion() is not implemented for ${this.name}`)
|
||||
}
|
||||
|
||||
static get category() {
|
||||
return 'social'
|
||||
}
|
||||
|
||||
async fetch({ schema, options }) {
|
||||
const apiVersion = this.constructor.apiVersion
|
||||
// See https://keybase.io/docs/api/1.0/call/user/lookup.
|
||||
const url = `https://keybase.io/_/api/${apiVersion}/user/lookup.json`
|
||||
|
||||
return this._requestJson({
|
||||
url,
|
||||
schema,
|
||||
options,
|
||||
})
|
||||
}
|
||||
|
||||
transform({ data }) {
|
||||
if (data.status.code !== 0) {
|
||||
throw new NotFound({ prettyMessage: 'invalid username' })
|
||||
}
|
||||
|
||||
if (data.them.length === 0 || !data.them[0]) {
|
||||
throw new NotFound({ prettyMessage: 'profile not found' })
|
||||
}
|
||||
|
||||
return { user: data.them[0] }
|
||||
}
|
||||
}
|
||||
92
services/keybase/keybase-xlm.service.js
Normal file
92
services/keybase/keybase-xlm.service.js
Normal file
@@ -0,0 +1,92 @@
|
||||
'use strict'
|
||||
|
||||
const KeybaseProfile = require('./keybase-profile')
|
||||
const Joi = require('joi')
|
||||
const { nonNegativeInteger } = require('../validators')
|
||||
|
||||
const stellarAddressSchema = Joi.object({
|
||||
status: Joi.object({
|
||||
code: nonNegativeInteger.required(),
|
||||
}).required(),
|
||||
them: Joi.array()
|
||||
.items(
|
||||
Joi.object({
|
||||
stellar: Joi.object({
|
||||
primary: Joi.object({
|
||||
account_id: Joi.string(),
|
||||
})
|
||||
.required()
|
||||
.allow(null),
|
||||
}).required(),
|
||||
})
|
||||
.required()
|
||||
.allow(null)
|
||||
)
|
||||
.min(0)
|
||||
.max(1),
|
||||
}).required()
|
||||
|
||||
module.exports = class KeybaseXLM extends KeybaseProfile {
|
||||
static get apiVersion() {
|
||||
return '1.0'
|
||||
}
|
||||
|
||||
static get route() {
|
||||
return {
|
||||
base: 'keybase/xlm',
|
||||
pattern: ':username',
|
||||
}
|
||||
}
|
||||
|
||||
static get defaultBadgeData() {
|
||||
return {
|
||||
label: 'xlm',
|
||||
color: 'informational',
|
||||
}
|
||||
}
|
||||
|
||||
async handle({ username }) {
|
||||
const options = {
|
||||
form: {
|
||||
usernames: username,
|
||||
fields: 'stellar',
|
||||
},
|
||||
}
|
||||
|
||||
const data = await this.fetch({
|
||||
schema: stellarAddressSchema,
|
||||
options,
|
||||
})
|
||||
|
||||
const { user } = this.transform({ data })
|
||||
const accountId = user.stellar.primary.account_id
|
||||
|
||||
if (accountId == null) {
|
||||
return {
|
||||
message: 'no stellar address found',
|
||||
color: 'inactive',
|
||||
}
|
||||
}
|
||||
|
||||
return this.constructor.render({ address: accountId })
|
||||
}
|
||||
|
||||
static render({ address }) {
|
||||
return {
|
||||
message: address,
|
||||
}
|
||||
}
|
||||
|
||||
static get examples() {
|
||||
return [
|
||||
{
|
||||
title: 'Keybase XLM',
|
||||
namedParams: { username: 'skyplabs' },
|
||||
staticPreview: this.render({
|
||||
address: 'GCGH37DYONEBPGAZGCHJEZZF3J2Q3EFYZBQBE6UJL5QKTULCMEA6MXLA',
|
||||
}),
|
||||
keywords: ['stellar'],
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
42
services/keybase/keybase-xlm.tester.js
Normal file
42
services/keybase/keybase-xlm.tester.js
Normal file
@@ -0,0 +1,42 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('joi')
|
||||
const { withRegex } = require('../test-validators')
|
||||
|
||||
const t = (module.exports = require('../tester').createServiceTester())
|
||||
|
||||
t.create('existing stellar address')
|
||||
.get('/skyplabs.json')
|
||||
.expectJSONTypes(
|
||||
Joi.object({
|
||||
name: 'xlm',
|
||||
value: withRegex(/^(?!not found$)/),
|
||||
})
|
||||
)
|
||||
|
||||
t.create('unknown username')
|
||||
.get('/skyplabsssssss.json')
|
||||
.expectJSONTypes(
|
||||
Joi.object({
|
||||
name: 'xlm',
|
||||
value: 'profile not found',
|
||||
})
|
||||
)
|
||||
|
||||
t.create('invalid username')
|
||||
.get('/s.json')
|
||||
.expectJSONTypes(
|
||||
Joi.object({
|
||||
name: 'xlm',
|
||||
value: 'invalid username',
|
||||
})
|
||||
)
|
||||
|
||||
t.create('missing stellar address')
|
||||
.get('/test.json')
|
||||
.expectJSONTypes(
|
||||
Joi.object({
|
||||
name: 'xlm',
|
||||
value: 'no stellar address found',
|
||||
})
|
||||
)
|
||||
94
services/keybase/keybase-zec.service.js
Normal file
94
services/keybase/keybase-zec.service.js
Normal file
@@ -0,0 +1,94 @@
|
||||
'use strict'
|
||||
|
||||
const KeybaseProfile = require('./keybase-profile')
|
||||
const Joi = require('joi')
|
||||
const { nonNegativeInteger } = require('../validators')
|
||||
|
||||
const zcachAddressSchema = Joi.object({
|
||||
status: Joi.object({
|
||||
code: nonNegativeInteger.required(),
|
||||
}).required(),
|
||||
them: Joi.array()
|
||||
.items(
|
||||
Joi.object({
|
||||
cryptocurrency_addresses: Joi.object({
|
||||
zcash: Joi.array().items(
|
||||
Joi.object({
|
||||
address: Joi.string().required(),
|
||||
}).required()
|
||||
),
|
||||
})
|
||||
.required()
|
||||
.allow(null),
|
||||
})
|
||||
.required()
|
||||
.allow(null)
|
||||
)
|
||||
.min(0)
|
||||
.max(1),
|
||||
}).required()
|
||||
|
||||
module.exports = class KeybaseZEC extends KeybaseProfile {
|
||||
static get apiVersion() {
|
||||
return '1.0'
|
||||
}
|
||||
|
||||
static get route() {
|
||||
return {
|
||||
base: 'keybase/zec',
|
||||
pattern: ':username',
|
||||
}
|
||||
}
|
||||
|
||||
static get defaultBadgeData() {
|
||||
return {
|
||||
label: 'zec',
|
||||
color: 'informational',
|
||||
}
|
||||
}
|
||||
|
||||
async handle({ username }) {
|
||||
const options = {
|
||||
form: {
|
||||
usernames: username,
|
||||
fields: 'cryptocurrency_addresses',
|
||||
},
|
||||
}
|
||||
|
||||
const data = await this.fetch({
|
||||
schema: zcachAddressSchema,
|
||||
options,
|
||||
})
|
||||
|
||||
const { user } = this.transform({ data })
|
||||
const zcashAddresses = user.cryptocurrency_addresses.zcash
|
||||
|
||||
if (zcashAddresses == null || zcashAddresses.length === 0) {
|
||||
return {
|
||||
message: 'no zcash addresses found',
|
||||
color: 'inactive',
|
||||
}
|
||||
}
|
||||
|
||||
return this.constructor.render({ address: zcashAddresses[0].address })
|
||||
}
|
||||
|
||||
static render({ address }) {
|
||||
return {
|
||||
message: address,
|
||||
}
|
||||
}
|
||||
|
||||
static get examples() {
|
||||
return [
|
||||
{
|
||||
title: 'Keybase ZEC',
|
||||
namedParams: { username: 'skyplabs' },
|
||||
staticPreview: this.render({
|
||||
address: 't1RJDxpBcsgqAotqhepkhLFMv2XpMfvnf1y',
|
||||
}),
|
||||
keywords: ['zcash'],
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
42
services/keybase/keybase-zec.tester.js
Normal file
42
services/keybase/keybase-zec.tester.js
Normal file
@@ -0,0 +1,42 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('joi')
|
||||
const { withRegex } = require('../test-validators')
|
||||
|
||||
const t = (module.exports = require('../tester').createServiceTester())
|
||||
|
||||
t.create('existing zcash address')
|
||||
.get('/skyplabs.json')
|
||||
.expectJSONTypes(
|
||||
Joi.object({
|
||||
name: 'zec',
|
||||
value: withRegex(/^(?!not found$)/),
|
||||
})
|
||||
)
|
||||
|
||||
t.create('unknown username')
|
||||
.get('/skyplabsssssss.json')
|
||||
.expectJSONTypes(
|
||||
Joi.object({
|
||||
name: 'zec',
|
||||
value: 'profile not found',
|
||||
})
|
||||
)
|
||||
|
||||
t.create('invalid username')
|
||||
.get('/s.json')
|
||||
.expectJSONTypes(
|
||||
Joi.object({
|
||||
name: 'zec',
|
||||
value: 'invalid username',
|
||||
})
|
||||
)
|
||||
|
||||
t.create('missing zcash address')
|
||||
.get('/test.json')
|
||||
.expectJSONTypes(
|
||||
Joi.object({
|
||||
name: 'zec',
|
||||
value: 'no zcash addresses found',
|
||||
})
|
||||
)
|
||||
Reference in New Issue
Block a user