Skip to content

Commit

Permalink
[feature] validate phone on client (#882)
Browse files Browse the repository at this point in the history
  • Loading branch information
Bernardo Vieira authored Sep 12, 2023
1 parent f95c5fe commit dc3d873
Show file tree
Hide file tree
Showing 15 changed files with 168 additions and 79 deletions.
4 changes: 3 additions & 1 deletion packages/api/src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,10 @@ export default {
*/
attestations: {
issuerPrivateKey: validatedEnv.ATTESTATION_ISSUER_PRIVATE_KEY,
issuerAddressClient2: validatedEnv.ATTESTATION_ISSUER_ADDRESS_CLIENT2,
dekPrivateKey: validatedEnv.ATTESTATION_DEK_PRIVATE_KEY,
odisProxy: validatedEnv.ATTESTATION_ODIS_PROXY
odisProxy: validatedEnv.ATTESTATION_ODIS_PROXY,
federatedAttestations: validatedEnv.ATTESTATION_FEDERATED_ATTESTATIONS_PROXY
},

/**
Expand Down
4 changes: 3 additions & 1 deletion packages/api/src/config/validatenv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,14 +79,16 @@ function validateEnv() {
LEARN_AND_EARN_CONTRACT_ADDRESS: str({ devDefault: onlyOnTestEnv('xyz') }),
AWS_LAMBDA: bool({ default: false }),
SIGNATURE_EXPIRATION: num({ default: 15 }),
ADMIN_AUTHORISED_ADDRESSES: str(),
ADMIN_AUTHORISED_ADDRESSES: str({ default: '0x0' }),
SUBGRAPH_URL: str({ devDefault: onlyOnTestEnv('xyz') }),
// attestation service (ASv2)
ATTESTATION_ISSUER_PRIVATE_KEY: str({
devDefault: onlyOnTestEnv('xyz')
}),
ATTESTATION_DEK_PRIVATE_KEY: str({ devDefault: onlyOnTestEnv('xyz') }),
ATTESTATION_ODIS_PROXY: str({ devDefault: onlyOnTestEnv('xyz') }),
ATTESTATION_FEDERATED_ATTESTATIONS_PROXY: str({ default: '0x0' }),
ATTESTATION_ISSUER_ADDRESS_CLIENT2: str({ default: '0x0' }),
// twilio
TWILIO_ACCOUNT_SID: str({ devDefault: onlyOnTestEnv('xyz') }),
TWILIO_AUTH_TOKEN: str({ devDefault: onlyOnTestEnv('xyz') }),
Expand Down
13 changes: 7 additions & 6 deletions packages/api/src/controllers/v2/user.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import { Request, Response } from 'express';
import { getAddress } from '@ethersproject/address';
import { services } from '@impactmarket/core';

import { ListUserNotificationsRequestSchema } from '~validators/user';
import { RequestWithUser } from '~middlewares/core';
import { ValidatedRequest } from '~utils/queryValidator';
import { standardResponse } from '~utils/api';
import UserLogService from '~services/app/user/log';
import UserService from '~services/app/user';

class UserController {
private userService: services.app.UserServiceV2;
private userLogService: services.app.UserLogService;
private userService: UserService;
private userLogService: UserLogService;

constructor() {
this.userService = new services.app.UserServiceV2();
this.userLogService = new services.app.UserLogService();
this.userService = new UserService();
this.userLogService = new UserLogService();
}

public create = (req: RequestWithUser, res: Response) => {
Expand All @@ -35,7 +36,7 @@ class UserController {
overwrite,
recover
} = req.body;
const { clientId } = req;
const clientId = parseInt(req.headers['client-id'] as string, 10);
this.userService
.create(
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,31 @@
import { Attributes, Op, WhereOptions } from 'sequelize';
import { database, interfaces, services, subgraph, utils } from '@impactmarket/core';
import { ethers } from 'ethers';
import { getAddress } from '@ethersproject/address';

import { AppNotification } from '../../../interfaces/app/appNotification';
import { AppUserCreationAttributes, AppUserUpdate } from '../../../interfaces/app/appUser';
import { AppUserModel } from '../../../database/models/app/appUser';
import { BaseError } from '../../../utils/baseError';
import { LogTypes } from '../../../interfaces/app/appLog';
import { ProfileContentStorage } from '../../../services/storage';
import { UserRoles, getUserRoles } from '../../../subgraph/queries/user';
import { generateAccessToken } from '../../../utils/jwt';
import { getAllBeneficiaries } from '../../../subgraph/queries/beneficiary';
import { models } from '../../../database';
import { sendFirebasePushNotification } from '../../../utils/pushNotification';
import { utils } from '../../../..';
import { lookup } from '../../../services/attestation';
import UserLogService from './log';
import config from '../../../config';
import config from '../../../config/index';

const { models } = database;
const { getUserRoles } = subgraph.queries.user;
const { getAllBeneficiaries } = subgraph.queries.beneficiary;
const { ProfileContentStorage } = services.storage;

type AppNotification = interfaces.app.appNotification.AppNotification;
type AppUserCreationAttributes = interfaces.app.appUser.AppUserCreationAttributes;
type AppUserUpdate = interfaces.app.appUser.AppUserUpdate;
type AppUserModel = any;
const { LogTypes } = interfaces.app.appLog;

type UserRoles = {
beneficiary: { community: string; state: number; address: string } | null;
borrower: { id: string } | null;
manager: { community: string; state: number } | null;
councilMember: { state: number } | null;
ambassador: { communities: string[]; state: number } | null;
loanManager: { state: number } | null;
};

export default class UserService {
private userLogService = new UserLogService();
Expand Down Expand Up @@ -59,16 +69,19 @@ export default class UserService {
: false;

if (existsPhone) {
throw new BaseError('PHONE_CONFLICT', 'phone associated with another account');
throw new utils.BaseError('PHONE_CONFLICT', 'phone associated with another account');
}
}

if (!exists) {
// create new user
// including their phone number information, if it exists
user = await models.appUser.create(userParams);
// we could prevent this update, but we don't want to make the user wait
if (clientId === 2) {
// TODO: validate phone number with SocialConnect and save
lookup(userParams.address, config.attestations.issuerAddressClient2).then(verified =>
user.update({ phoneValidated: verified })
);
}
} else {
const findAndUpdate = async () => {
Expand All @@ -78,11 +91,11 @@ export default class UserService {
}))!;

if (!_user.active) {
throw new BaseError('INACTIVE_USER', 'user is inactive');
throw new utils.BaseError('INACTIVE_USER', 'user is inactive');
}

if (_user.deletedAt) {
throw new BaseError('DELETION_PROCESS', 'account in deletion process');
throw new utils.BaseError('DELETION_PROCESS', 'account in deletion process');
}

const updateFields: {
Expand Down Expand Up @@ -125,7 +138,7 @@ export default class UserService {

this._updateLastLogin(user.id);

const token = generateAccessToken(userParams.address, user.id);
const token = utils.jwt.generateAccessToken(userParams.address, user.id);
const jsonUser = user.toJSON();
return {
...jsonUser,
Expand All @@ -146,7 +159,7 @@ export default class UserService {
]);

if (user === null) {
throw new BaseError('USER_NOT_FOUND', 'user not found');
throw new utils.BaseError('USER_NOT_FOUND', 'user not found');
}
const notificationsCount = await models.appNotification.count({
where: {
Expand All @@ -167,7 +180,7 @@ export default class UserService {
const { ambassador, manager, councilMember, loanManager } = await this._userRoles(authoriedAddress);

if (!ambassador && !manager && !councilMember && !loanManager) {
throw new BaseError(
throw new utils.BaseError(
'UNAUTHORIZED',
'user must be ambassador, ubi manager, loand manager or council member'
);
Expand All @@ -180,15 +193,15 @@ export default class UserService {
if (user.phone) {
const existsPhone = await this._existsAccountByPhone(user.phone, user.address);

if (existsPhone) throw new BaseError('PHONE_CONFLICT', 'phone associated with another account');
if (existsPhone) throw new utils.BaseError('PHONE_CONFLICT', 'phone associated with another account');
}

const updated = await models.appUser.update(user, {
returning: true,
where: { address: user.address }
});
if (updated[0] === 0) {
throw new BaseError('UPDATE_FAILED', 'user was not updated!');
throw new utils.BaseError('UPDATE_FAILED', 'user was not updated!');
}

this.userLogService.create(updated[1][0].id, LogTypes.EDITED_PROFILE, user);
Expand Down Expand Up @@ -222,7 +235,7 @@ export default class UserService {
const roles = await getUserRoles(address);

if (roles.manager !== null && roles.manager.state === 0) {
throw new BaseError('MANAGER', "Active managers can't delete accounts");
throw new utils.BaseError('MANAGER', "Active managers can't delete accounts");
}

const updated = await models.appUser.update(
Expand All @@ -238,7 +251,7 @@ export default class UserService {
);

if (updated[0] === 0) {
throw new BaseError('UPDATE_FAILED', 'User was not updated');
throw new utils.BaseError('UPDATE_FAILED', 'User was not updated');
}
return updated[1][0].toJSON();
}
Expand All @@ -263,7 +276,7 @@ export default class UserService {
const userRoles = await getUserRoles(user);

if (!userRoles.ambassador || userRoles.ambassador.communities.length === 0) {
throw new BaseError('COMMUNITY_NOT_FOUND', 'no community found for this ambassador');
throw new utils.BaseError('COMMUNITY_NOT_FOUND', 'no community found for this ambassador');
}

const { communities } = userRoles.ambassador;
Expand All @@ -279,7 +292,7 @@ export default class UserService {
});

if (!community?.contractAddress || communities.indexOf(community?.contractAddress?.toLowerCase()) === -1) {
throw new BaseError('NOT_AMBASSADOR', 'user is not an ambassador of this community');
throw new utils.BaseError('NOT_AMBASSADOR', 'user is not an ambassador of this community');
}
addresses.push(community.contractAddress);
} else {
Expand Down Expand Up @@ -322,7 +335,7 @@ export default class UserService {
}
);
} catch (error) {
throw new BaseError('UNEXPECTED_ERROR', error.message);
throw new utils.BaseError('UNEXPECTED_ERROR', error.message);
}
}

Expand Down Expand Up @@ -392,7 +405,7 @@ export default class UserService {
}
);
if (updated[0] === 0) {
throw new BaseError('UPDATE_FAILED', 'notifications were not updated!');
throw new utils.BaseError('UPDATE_FAILED', 'notifications were not updated!');
}
return true;
}
Expand All @@ -414,12 +427,14 @@ export default class UserService {
}
}
});
sendFirebasePushNotification(
users.map(el => el.walletPNT!),
title,
body,
data
).catch(error => utils.Logger.error('sendFirebasePushNotification' + error));
utils.pushNotification
.sendFirebasePushNotification(
users.map(el => el.walletPNT!),
title,
body,
data
)
.catch(error => utils.Logger.error('sendFirebasePushNotification' + error));
} else if (communitiesIds && communitiesIds.length) {
const communities = await models.community.findAll({
attributes: ['contractAddress'],
Expand Down Expand Up @@ -454,14 +469,16 @@ export default class UserService {
}
}
});
sendFirebasePushNotification(
users.map(el => el.walletPNT!),
title,
body,
data
).catch(error => utils.Logger.error('sendFirebasePushNotification' + error));
utils.pushNotification
.sendFirebasePushNotification(
users.map(el => el.walletPNT!),
title,
body,
data
)
.catch(error => utils.Logger.error('sendFirebasePushNotification' + error));
} else {
throw new BaseError('INVALID_OPTION', 'invalid option');
throw new utils.BaseError('INVALID_OPTION', 'invalid option');
}
}

Expand Down Expand Up @@ -504,7 +521,7 @@ export default class UserService {

await Promise.all(promises);
} catch (error) {
throw new BaseError('UNEXPECTED_ERROR', error.message);
throw new utils.BaseError('UNEXPECTED_ERROR', error.message);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { database, interfaces, subgraph, utils } from '@impactmarket/core';
import { ethers } from 'ethers';

import { AppLog, LogTypes } from '../../../interfaces/app/appLog';
import { BaseError } from '../../../utils/baseError';
import { getUserRoles } from '../../../subgraph/queries/user';
import { models } from '../../../database';
const { models } = database;
const { getUserRoles } = subgraph.queries.user;
type AppLog = any;

export default class UserLogService {
public async create(userId: number, type: LogTypes, detail: object, communityId?: number) {
public async create(userId: number, type: interfaces.app.appLog.LogTypes, detail: object, communityId?: number) {
try {
await models.appLog.create({
userId,
Expand All @@ -20,7 +20,7 @@ export default class UserLogService {
}

public async get(ambassadorAddress: string, type: string, entity: string): Promise<AppLog[]> {
if (type === LogTypes.EDITED_COMMUNITY) {
if (type === interfaces.app.appLog.LogTypes.EDITED_COMMUNITY) {
const community = await models.community.findOne({
attributes: ['id'],
where: {
Expand All @@ -30,7 +30,7 @@ export default class UserLogService {
});

if (!community) {
throw new BaseError('COMMUNITY_NOT_FOUND', 'community not found');
throw new utils.BaseError('COMMUNITY_NOT_FOUND', 'community not found');
}

return models.appLog.findAll({
Expand All @@ -53,7 +53,7 @@ export default class UserLogService {
? roles.manager.community
: null;
if (!contractAddress) {
throw new BaseError('USER_NOT_FOUND', 'user not found');
throw new utils.BaseError('USER_NOT_FOUND', 'user not found');
}
const community = await models.community.findOne({
attributes: ['id'],
Expand All @@ -63,7 +63,7 @@ export default class UserLogService {
}
});
if (!community) {
throw new BaseError('COMMUNITY_NOT_FOUND', 'community not found');
throw new utils.BaseError('COMMUNITY_NOT_FOUND', 'community not found');
}

return models.appLog.findAll({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
[
{
"constant": true,
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
},
{
"internalType": "address[]",
"name": "trustedIssuers",
"type": "address[]"
}
],
"name": "lookupIdentifiers",
"outputs": [
{
"internalType": "uint256[]",
"name": "countsPerIssuer",
"type": "uint256[]"
},
{
"internalType": "bytes32[]",
"name": "identifiers",
"type": "bytes32[]"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
}
]
Loading

0 comments on commit dc3d873

Please sign in to comment.