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

[feature] validate phone on client #882

Merged
merged 7 commits into from
Sep 12, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
2 changes: 2 additions & 0 deletions packages/api/src/config/validatenv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ function validateEnv() {
}),
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
Loading