Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wallet cooldown #597

Merged
merged 3 commits into from Apr 5, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions .travis.yml
Expand Up @@ -30,6 +30,8 @@ env:
- "TESTING_COHORTS=test"
- "TOKEN_LIST=foobarfoobar"
- "UPHOLD_DONOR_CARD_ID=6654ecb0-6079-4f6c-ba58-791cc890a561"
- "WALLET_COOLDOWN_BYPASS_TOKEN=fb9dcd45-36f5-4c2d-b511-90fa1f0cadae"
- "WALLET_COOLDOWN_HRS=24"
- "VOTING_COHORTS=control,grant,test,ads,safetynet"
- secure: "JQ9WipBrcCPtXFwv/fmzhv8LPz/tx/jCPpeTK0KmSZr4Yqs5kMG0YGH3QeT1YDpiRakW9X5us9tsnsfHuslvZ8Z565lc0wIK7b7CUcEzVuBts6cfVQtOpqKKYjw6jyq5TraTYawYGUfeaCkP+35oM/1fs4vPy6MbH5Plzg6f9iMfB+BFysRT20JYKXcW0LfihHRKStaAJYKDD2mpdY4kStu7vYB66LgR6Solw1IUwgJzfKst/dM6PJspfmNkFaHCoBNMEYcUJmeJPqmLCnfIA+eRCrSMfHOChjzCV6JbmcD11FOydISEhdt67X+5TDArePYVssrj7NygVlrn8TOvk6InXuul6n+oIpS0vz5e5QdxrReLLwb+WJPK7xKWD56Ko2gfTwtEUdevLzQhKfPMs5wW5ujjlcjAqi5PmC87Q+bxCjet19EdJ4j09y2NSKRo1LMzu1WIM6o1mIatha0vJEHa05XAUsuD9lCGrt/aG4DLYh5Q5gJP/SYDoMl+wJku254KJN+WesxaSREhgdqtV13GFnMofI9r/vq4wKrlcbsMh3UD337NZlNVZzONINeJ4afN3TRK+LS8bHGsss/ZpLcnfT0uYMXzneQQhkKmKWkss8VOLEZ2ccLRG0LNWsOJXznBE/SqXaxSK4NkgXQDyfVUuAj7GPyIDX6hygsguyA="
- secure: "be8ASUNkGT1MpO/wfwVQKuo0Absf5xQAnjXRmsrM105n+vGXdq1c/fl13wow7d/PQtR27tNlJ5UmoeROUCBLlOKm+bWWZ7wFThsxOQtHk3yJoaVtIApo5cwEQcMi/92zWQKDwO7ZuK+xY9oLiYng2yayfuxBUEzWlNxS0cMsmq6Xnf1qG8USVCqvo8gQp/X8hliscFWRbCJ+lzggmlIsoTe6RElF9N3yXngvWRJY/GDjOe3FD2wJNDoE8wYwHcxKvfcdL0c8dWhHl5KC366ZSiBMJHrwv2aVjUgwQ2Azsdehxy1bsqyiH2bS/ifH8FUosjMA/g8qRB89EgaLCS5NndIVl1q0miuRv20kMaF6syTOH3HyDi/o2L9tg1Z/XSuNSVI2o+A8yJfKiY1AwjCR8aQ+uyKj9kOg7nTlBElHrOcAOaiC0Rg13TZ2230IbFanQFIqDL0B8xQx1EAxsArgWIrebqSd1QbVYKEHE5+2dWXExPX+7e217rFvEz5Ltxq1U9IdHHnmcTxUrylr800ub2PtOyZvRNbBFXq5tDpENLl/lV8yf0Qm2q+8jUvqXZIG0akIqC5b0cpHEtxJcAGvfqFx8vZUV1WW4TVbxApmndSGFw2bF6psx6SJUDrdFfl87u1Rb7wWtesDXAMrfIphlr0AcH2P7XDqxAjifuQgtdk="
Expand Down
2 changes: 2 additions & 0 deletions base.yml
Expand Up @@ -70,4 +70,6 @@ services:
- UPHOLD_DONOR_CARD_ID
- UPHOLD_ENVIRONMENT
- VOTING_COHORTS
- WALLET_COOLDOWN_BYPASS_TOKEN
- WALLET_COOLDOWN_HRS
- YOUTUBE_API_KEY
32 changes: 26 additions & 6 deletions ledger/controllers/grants.js
Expand Up @@ -6,14 +6,18 @@ const bson = require('bson')
const underscore = require('underscore')
const uuidV4 = require('uuid/v4')
const wreck = require('wreck')

const {
cooldownOffset
} = require('../lib/grants')
const utils = require('bat-utils')
const braveJoi = utils.extras.joi
const braveHapi = utils.extras.hapi
const braveUtils = utils.extras.utils
const whitelist = utils.hapi.auth.whitelist

const rateLimitEnabled = process.env.NODE_ENV === 'production'
const { NODE_ENV } = process.env
const isProduction = NODE_ENV === 'production'
const rateLimitEnabled = isProduction

const qalist = { addresses: process.env.IP_QA_WHITELIST && process.env.IP_QA_WHITELIST.split(',') }

Expand Down Expand Up @@ -251,9 +255,11 @@ const getGrant = (protocolVersion) => (runtime) => {
}
}
}

const lang = request.query.lang
const paymentId = request.query.paymentId
const {
lang,
paymentId,
bypassCooldown
} = request.query
const languages = l10nparser.parse(lang)
const query = {
active: true,
Expand All @@ -265,6 +271,7 @@ const getGrant = (protocolVersion) => (runtime) => {
const promotions = runtime.database.get('promotions', debug)
const wallets = runtime.database.get('wallets', debug)
let entries, promotionIds, wallet
let walletTooYoung = false

if (qaOnlyP(request)) return reply(boom.notFound())

Expand All @@ -276,6 +283,7 @@ const getGrant = (protocolVersion) => (runtime) => {
wallet.grants.forEach((grant) => { promotionIds.push(grant.promotionId) })
}
underscore.extend(query, { promotionId: { $nin: promotionIds } })
walletTooYoung = walletCooldown(wallet, bypassCooldown)
}

if (protocolVersion === 4 && !paymentId) {
Expand All @@ -299,7 +307,7 @@ const getGrant = (protocolVersion) => (runtime) => {
underscore.extend(query, { providerId: wallet.addresses.CARD_ID })
}
const counted = await grants.count(query)
if (counted !== 0) {
if (counted !== 0 && !walletTooYoung) {
filteredPromotions.push({ promotionId, type })
}
}
Expand Down Expand Up @@ -372,6 +380,7 @@ v4.read = {

validate: {
query: {
bypassCooldown: Joi.string().guid().optional().description('a token to bypass the wallet cooldown time'),
lang: Joi.string().regex(localeRegExp).optional().default('en').description('the l10n language'),
paymentId: Joi.string().guid().optional().description('identity of the wallet')
}
Expand Down Expand Up @@ -1163,3 +1172,14 @@ function uploadTypedGrants (protocolVersion, uploadSchema, contentSchema) {
reply({})
}
}

function walletCooldown (wallet, bypassCooldown) {
const { _id } = wallet
const { WALLET_COOLDOWN_BYPASS_TOKEN } = process.env
if (isProduction || bypassCooldown !== WALLET_COOLDOWN_BYPASS_TOKEN) {
const offset = cooldownOffset()
const createdTime = braveUtils.createdTimestamp(_id)
return createdTime > (new Date() - offset)
}
return false
}
15 changes: 15 additions & 0 deletions ledger/lib/grants.js
@@ -0,0 +1,15 @@
const _ = require('underscore')
const { WALLET_COOLDOWN_HRS } = process.env
module.exports = {
defaultCooldownHrs,
cooldownOffset
}

function defaultCooldownHrs (hours) {
const hrs = _.isUndefined(hours) ? WALLET_COOLDOWN_HRS : hours
return hrs ? (+hrs || 0) : 24
}

function cooldownOffset (hours = defaultCooldownHrs()) {
return hours * 60 * 60 * 1000
}
33 changes: 30 additions & 3 deletions test/ledger/grants.integration.test.js
Expand Up @@ -18,11 +18,16 @@ import {
timeout,
uint8tohex
} from 'bat-utils/lib/extras-utils'
import {
defaultCooldownHrs,
cooldownOffset
} from '../../ledger/lib/grants'

test.before(cleanDbs)
test.after(cleanDbs)
test.afterEach.always(cleanDbs)

const bypassCooldown = process.env.WALLET_COOLDOWN_BYPASS_TOKEN
const promotionId = 'c96c39c8-77dd-4b2d-a8df-2ecf824bc9e9'
// expired grant
const expired = {'grants': ['eyJhbGciOiJFZERTQSIsImtpZCI6IiJ9.eyJhbHRjdXJyZW5jeSI6IkJBVCIsImdyYW50SWQiOiI0Y2ZjMzFmYy1mYjE1LTRmMTUtOTc0Zi0zNzJiMmI0YzBkYjYiLCJwcm9iaSI6IjMwMDAwMDAwMDAwMDAwMDAwMDAwIiwicHJvbW90aW9uSWQiOiJjOTZjMzljOC03N2RkLTRiMmQtYThkZi0yZWNmODI0YmM5ZTkiLCJtYXR1cml0eVRpbWUiOjE1MjY5NDE0MDAsImV4cGlyeVRpbWUiOjE1MjUxNzYwMDB9.iZBTNb9zilKubYYwYuc9MIUHZq0iv-7DsmnNu0GakeiEjcNqgbgbg-Wc2dowlMmMyjRbXjDUIC8rK4FiIqH8CQ'], 'promotions': [{promotionId, 'priority': 0, 'active': true, 'minimumReconcileTimestamp': 1526941400000}]}
Expand Down Expand Up @@ -366,13 +371,17 @@ test('protocolVersion 4 does not send back ads when none are available', async (
await ledgerAgent.post(url).send(adGrants).expect(ok)

// get available grant
await ledgerAgent
.get('/v4/grants')
.query({ paymentId })
.expect(404)
const {
body: {
grants: promotions
}
} = await ledgerAgent
.get(`/v4/grants`)
.query({ paymentId })
.query({ paymentId, bypassCooldown })
.expect(ok)
t.is(promotions.length, 1, 'only one promotion should be sent back')

Expand Down Expand Up @@ -470,13 +479,17 @@ test('protocolVersion 4 can claim both ads and ugp grants', async (t) => {
await ledgerAgent.post(url).send(adGrants).expect(ok)

// get available grant
await ledgerAgent
.get('/v4/grants')
.query({ paymentId })
.expect(404)
const {
body: {
grants: promotions
}
} = await ledgerAgent
.get(`/v4/grants`)
.query({ paymentId })
.query({ paymentId, bypassCooldown })
.expect(ok)
t.is(promotions.length, 2, '2 promotions should be sent back')

Expand Down Expand Up @@ -580,7 +593,7 @@ test('protocolVersion 4 can claim both ads and ugp grants even on claim v2', asy
}
} = await ledgerAgent
.get(`/v4/grants`)
.query({ paymentId })
.query({ paymentId, bypassCooldown })
.expect(ok)
t.is(promotions.length, 2, '2 promotions should be sent back')

Expand All @@ -600,6 +613,20 @@ test('protocolVersion 4 can claim both ads and ugp grants even on claim v2', asy
}
})

test('default cooldown hrs', async (t) => {
t.is(defaultCooldownHrs(), defaultCooldownHrs(process.env.WALLET_COOLDOWN_HRS), 'uses env var for default')
t.is(defaultCooldownHrs(12), 12, 'can be passed number')
t.is(defaultCooldownHrs('12'), 12, 'can be passed numeric string')
t.is(defaultCooldownHrs('0'), 0, 'can be passed falsey numeric string')
t.is(defaultCooldownHrs('a'), 0, 'defaults to 0 if passed non numeric values')
})

test('cooldown offset', async (t) => {
t.is(cooldownOffset(), cooldownOffset(defaultCooldownHrs()), 'calculates hours to offset in terms of milliseconds')
t.is(cooldownOffset(12), 12 * 60 * 60 * 1000, 'gives back in ms')
t.is(cooldownOffset({}), NaN, 'only takes numeric values')
})

async function resolveCaptcha (wallets, {
version = 4,
paymentId,
Expand Down