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

feat: Account bootstrapping [DEV-3051] #306

Merged
merged 11 commits into from
Aug 10, 2023
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,15 @@ By default, `ENABLE_AUTHENTICATION` is set to off/`false`. To enable external Ve
5. **Miscellaneous**
1. `COOKIE_SECRET`: Secret for cookie encryption.

#### Faucet settings

This section describes bootstrapping things for newcomers accounts. If it's enabled the CredentialService auto-populates some tokens on the testnet for making the process simpler.

1. `FAUCET_ENABLED` - enable/disable such functionality (`false` by default)
2. `FAUCET_URI` - URI when the faucet service is located (`https://faucet-api.cheqd.network/credit` by default)
3. `FAUCET_DENOM` - the denom of token to assign (`ncheq` by default)
4. `TESTNET_MINIMUM_BALANCE` - the minimum amount of tokens for being on testnet account. Be default it's amount, required for creating a DID

### 3rd Party Connectors

The app supports 3rd party connectors for credential storage and delivery.
Expand Down
10 changes: 10 additions & 0 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ ARG VERIDA_NETWORK=testnet
ARG POLYGON_RPC_URL=https://rpc-mumbai.maticvigil.com
ARG VERIDA_PRIVATE_KEY
ARG POLYGON_PRIVATE_KEY
ARG FAUCET_ENABLED=false
ARG FAUCET_URI=https://faucet-api.cheqd.network/credit
ARG FAUCET_DENOM=ncheq
ARG TESTNET_MINIMUM_BALANCE=50000000000

# Environment variables: base configuration
ENV NPM_CONFIG_LOGLEVEL ${NPM_CONFIG_LOGLEVEL}
Expand Down Expand Up @@ -106,6 +110,12 @@ ENV LOGTO_MANAGEMENT_API ${LOGTO_MANAGEMENT_API}
ENV LOGTO_DEFAULT_ROLE_ID ${LOGTO_DEFAULT_ROLE_ID}
ENV LOGTO_WEBHOOK_SECRET ${LOGTO_WEBHOOK_SECRET}

# Faucet setup
ENV FAUCET_ENABLED ${FAUCET_ENABLED}
ENV FAUCET_URI ${FAUCET_URI}
ENV FAUCET_DENOM ${FAUCET_DENOM}
ENV TESTNET_MINIMUM_BALANCE ${TESTNET_MINIMUM_BALANCE}

# Environment variables: Verida connector
ENV ENABLE_VERIDA_CONNECTOR ${ENABLE_VERIDA_CONNECTOR}
ENV VERIDA_NETWORK ${VERIDA_NETWORK}
Expand Down
2 changes: 1 addition & 1 deletion src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ class App {
app.get(`/account`, new AccountController().get)

// LogTo webhooks
app.post(`/account/set-default-role`, LogToWebHook.verifyHookSignature, new AccountController().setupDefaultRole)
app.post(`/account/bootstrap`, LogToWebHook.verifyHookSignature, new AccountController().bootstrap)

// LogTo user info
app.get('/auth/user-info', async (req, res) => {
Expand Down
87 changes: 87 additions & 0 deletions src/controllers/customer.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import type { Request, Response } from 'express'
import { StargateClient } from "@cosmjs/stargate"

import { CustomerService } from '../services/customer.js'
import { LogToHelper } from '../middleware/auth/logto.js'
import { FaucetHelper } from '../helpers/faucet.js'
import { StatusCodes } from 'http-status-codes'
import { LogToWebHook } from '../middleware/hook.js'
import { NetworkType } from '../types/types.js'

export class AccountController {

Expand Down Expand Up @@ -36,8 +40,17 @@ export class AccountController {
error: `Error creating customer. Please try again`
})
}
// Send some tokens for testnet
if (process.env.FAUCET_ENABLED === 'true') {
const resp = await FaucetHelper.delegateTokens(customer.address)
if (resp.status !== StatusCodes.OK) {
return response.status(resp.status).json({
error: resp.error})
}
}
return response.status(StatusCodes.OK).json({
customerId: customer.customerId,
address: customer.address,
})
} catch (error) {
return response.status(StatusCodes. INTERNAL_SERVER_ERROR).json({
Expand Down Expand Up @@ -106,4 +119,78 @@ export class AccountController {
}
return response.status(StatusCodes.BAD_REQUEST).json({})
}

public async bootstrap(request: Request, response: Response) {
// 1. Check that the customer exists
// 1.1 If not, create it
// 2. Get the user Roles
// 2.1 If list of roles is empty - assign default role
// 2.2 Create custom_data and update the userInfo (send it to the LogTo)
// 3. Check the token balance for Testnet account
// 3.1 If it's less then required for DID creation - assign new portion from testnet-faucet
const customerId: string = response.locals.customerId || LogToWebHook.getCustomerId(request);
const logToHelper = new LogToHelper()
const _r = await logToHelper.setup()
if (_r.status !== StatusCodes.OK) {
return response.status(StatusCodes.BAD_GATEWAY).json({
error: _r.error})
}
// 1. Check that the customer exists
let customer : any = await CustomerService.instance.get(customerId)
if (!customer) {
customer = await CustomerService.instance.create(customerId)
}
// 2. Get the user's roles
const roles = await logToHelper.getRolesForUser(customerId)
if (roles.status !== StatusCodes.OK) {
return response.status(StatusCodes.BAD_GATEWAY).json({
error: roles.error})
}

// 2.1 If list of roles is empty and the user is not suspended - assign default role
if (roles.data.length === 0 && !LogToWebHook.isUserSuspended(request)) {
const _r = await logToHelper.setDefaultRoleForUser(customerId)
if (_r.status !== StatusCodes.OK) {
return response.status(StatusCodes.BAD_GATEWAY).json({
error: _r.error})
}
}

const customDataFromLogTo = await logToHelper.getCustomData(customerId)

// 2.2 Create custom_data and update the userInfo (send it to the LogTo)
if (Object.keys(customDataFromLogTo.data).length === 0 && customer.address) {
const customData = {
cosmosAccounts: {
testnet: customer.address
}
}
const _r = await logToHelper.updateCustomData(customerId, customData)
if (_r.status !== 200) {
return response.status(_r.status).json({
error: _r.error})
}
}

// 3. Check the token balance for Testnet account
if (customer.address && process.env.FAUCET_ENABLED === 'true') {
const balance = await AccountController.checkBalance(customer.address)
if (!balance || +balance.amount < process.env.TESTNET_MINIMUM_BALANCE) {
// 3.1 If it's less then required for DID creation - assign new portion from testnet-faucet
const resp = await FaucetHelper.delegateTokens(customer.address)
if (resp.status !== StatusCodes.OK) {
return response.status(StatusCodes.BAD_GATEWAY).json({
error: resp.error})
}
}
}
return response.status(StatusCodes.OK).json({})
}

public static async checkBalance(address: string, network = NetworkType.Testnet) {
const rpc_address = network === NetworkType.Testnet ? process.env.TESTNET_RPC_URL : process.env.MAINNET_RPC_URL
const client = await StargateClient.connect(rpc_address)
const balances = await client.getAllBalances(address)
return balances[0]
}
}
27 changes: 27 additions & 0 deletions src/helpers/faucet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { ICommonErrorResponse } from "../types/authentication.js";
import { DEFAULT_FAUCET_DENOM, DEFAULT_FAUCET_URI } from "../types/constants.js";

export class FaucetHelper {

// ...
static async delegateTokens(address: string): Promise<ICommonErrorResponse> {
const faucetURI = DEFAULT_FAUCET_URI
const faucetBody = {
"denom": DEFAULT_FAUCET_DENOM,
"address": address,
}
const response = await fetch(faucetURI, {
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(faucetBody),
method: "POST"
});
return {
status: response.status,
error: await response.text(),
data: {}
}
}
// ...
}
2 changes: 1 addition & 1 deletion src/middleware/auth/base-auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export abstract class AbstractAuthHandler implements IAuthResourceHandler
'/user',
'/static',
'/logto',
'/account/set-default-role']
'/account/bootstrap']

constructor () {
this.nextHandler = {} as IAuthResourceHandler
Expand Down
Loading