diff --git a/client/src/app/screens/screen-receive/screen-receive.ts b/client/src/app/screens/screen-receive/screen-receive.ts
index da09c80..1d97b99 100644
--- a/client/src/app/screens/screen-receive/screen-receive.ts
+++ b/client/src/app/screens/screen-receive/screen-receive.ts
@@ -17,7 +17,7 @@ export class ScreenReceive {
amount: number = 0;
get shareableLink(): string {
const currentDomain = window.location.origin;
- return `${currentDomain}/send/${this.api.currentUser}?amount=${this.amount}`;
+ return `${currentDomain}/send/${this.api.currentUser.id}?amount=${this.amount}`;
}
copyLink() {
diff --git a/client/src/app/screens/screen-send/screen-send.ts b/client/src/app/screens/screen-send/screen-send.ts
index a511156..47331cd 100644
--- a/client/src/app/screens/screen-send/screen-send.ts
+++ b/client/src/app/screens/screen-send/screen-send.ts
@@ -22,8 +22,8 @@ export class ScreenSend {
sendMoney() {
this.api.send(this.amount, this.recipient, this.reference).subscribe({
next:()=> {
- this.clear()
this.notify.success(`Sent ${this.amount} to ${this.recipient}`);
+ this.clear()
},
error:(err)=> {
if(err.status == 404){
diff --git a/client/src/app/services/api.ts b/client/src/app/services/api.ts
index e69be4a..781f7ed 100644
--- a/client/src/app/services/api.ts
+++ b/client/src/app/services/api.ts
@@ -4,6 +4,8 @@ import { BehaviorSubject, catchError, map, Observable, of, tap } from 'rxjs';
import Transaction from '@model/transaction'
import { SendRequest, SendResponse } from '@message/Send';
import { TransactionsRequest } from '@message/Transactions';
+import { LoginResponse } from '@message/Login';
+import Account from '@model/user';
@Injectable({
providedIn: 'root',
@@ -13,15 +15,19 @@ export class APIService {
private isAuthenticatedSubject = new BehaviorSubject
(false);
isAuthenticated$ = this.isAuthenticatedSubject.asObservable();
- // TODO use real user here
- currentUser = 'DemoUser';
+ currentUser!: Account;
+ ownedAccounts!: Account[];
constructor(private http: HttpClient){}
login(username: string, password: string): Observable{
- return this.http.post(`${this.apiUrl}/auth/login`,{ 'username': username, 'password': password}).pipe(
+ return this.http.post(`${this.apiUrl}/auth/login`,{ 'username': username, 'password': password}).pipe(
tap({
- next: () => this.isAuthenticatedSubject.next(true),
+ next: (resp) => {
+ this.isAuthenticatedSubject.next(true);
+ this.currentUser = resp.user;
+ this.ownedAccounts = resp.ownedAccounts;
+ },
error: () => this.isAuthenticatedSubject.next(false)
})
);
@@ -50,7 +56,8 @@ export class APIService {
return this.http.get(`${this.apiUrl}/transactions`);
}
send(amount: number, recipientID: string, reference: string = ""): Observable{
- let request: SendRequest = {amount, recipientID, reference};
+
+ let request: SendRequest = {senderID: this.currentUser.id, amount, recipientID, reference};
return this.http.post(`${this.apiUrl}/send`, request);
}
}
diff --git a/server/src/messages/Login.ts b/server/src/messages/Login.ts
index e8c68aa..a8f5c29 100644
--- a/server/src/messages/Login.ts
+++ b/server/src/messages/Login.ts
@@ -8,6 +8,6 @@ export class LoginRequest{
export class LoginResponse{
constructor(
public user: Account,
- public ownedBusinesses: Account[],
+ public ownedAccounts: Account[],
){}
}
\ No newline at end of file
diff --git a/server/src/messages/Send.ts b/server/src/messages/Send.ts
index 56384f0..7259fa4 100644
--- a/server/src/messages/Send.ts
+++ b/server/src/messages/Send.ts
@@ -1,5 +1,6 @@
export class SendRequest{
constructor(
+ public senderID: string,
public recipientID: string,
public amount: number,
public reference: string
diff --git a/server/src/messages/Transactions.ts b/server/src/messages/Transactions.ts
index bebde2d..c762573 100644
--- a/server/src/messages/Transactions.ts
+++ b/server/src/messages/Transactions.ts
@@ -2,6 +2,7 @@ import Transaction from "../model/transaction";
export class TransactionsRequest{
constructor(
+ public forId: string,
public offset = 0,
public count = 50,
){}
diff --git a/server/src/model/user.ts b/server/src/model/user.ts
index 471a017..28f3e88 100644
--- a/server/src/model/user.ts
+++ b/server/src/model/user.ts
@@ -1,6 +1,8 @@
import { Table, Column, Model, DataType, Scopes, DefaultScope, DeletedAt, BelongsToMany, ForeignKey} from 'sequelize-typescript';
+@Table
@DefaultScope(() => ({
+ //FIXME getting account still includes password
attributes:{ exclude: ['password']}
}))
@Scopes(() => ({
@@ -8,38 +10,57 @@ import { Table, Column, Model, DataType, Scopes, DefaultScope, DeletedAt, Belong
attributes: {include: ['password']}
}
}))
-@Table
export default class Account extends Model{
- @Column({primaryKey: true, unique: true, allowNull: false})
- declare id: string;
+ @Column({primaryKey: true, unique: true, allowNull: false})
+ declare id: string;
- @Column
- declare isBusiness: boolean;
+ @Column
+ declare isBusiness: boolean;
- @Column
- declare displayName: string;
+ @Column
+ declare displayName: string;
- @Column(DataType.DECIMAL(20,2))
- declare balance: number;
+ @Column(DataType.DECIMAL(20,2))
+ declare balance: number;
- @Column
- declare password: string;
-
- @DeletedAt
- declare deletedAt: Date | null;
-
+ @Column
+ declare password: string;
+
+ @DeletedAt
+ declare deletedAt: Date | null;
+
+ @BelongsToMany(() => Account, () => BusinessOwnership, 'ownerAccountId', 'ownedAccountId')
+ declare ownedBusinesses: Account[];
+
+ @BelongsToMany(() => Account, () => BusinessOwnership, 'ownedAccountId', 'ownerAccountId')
+ declare owners: Account[];
+
+ override toJSON() {
+ const values = { ...this.get() };
+ delete values.password;
+ return values;
+ }
}
+
@Table
export class BusinessOwnership extends Model {
@ForeignKey(() => Account)
@Column
- ownerAccountId!: number;
+ declare ownerAccountId: string;
@ForeignKey(() => Account)
@Column
- ownedAccountId!: number;
+ declare ownedAccountId: string;
@DeletedAt
declare deletedAt: Date | null;
+}
+
+export async function getOwnedAccounts(user: Account){
+ let q = await Account.findByPk(user.id, {
+ include: ['ownedBusinesses'],
+ });
+ let ownedAccounts = q?.ownedBusinesses;
+ return ownedAccounts ?? [];
}
\ No newline at end of file
diff --git a/server/src/routes/auth.ts b/server/src/routes/auth.ts
index d88e0e4..b34ae94 100644
--- a/server/src/routes/auth.ts
+++ b/server/src/routes/auth.ts
@@ -1,10 +1,10 @@
import { compare } from 'bcrypt';
import express from 'express';
import { logger } from '../util/logging';
-import Account from '../model/user';
+import Account, { getOwnedAccounts } from '../model/user';
import { Scope } from '../util/db';
import { getJWT, requireAuth } from '../util/auth';
-import { LoginRequest } from '../messages/Login';
+import { LoginRequest, LoginResponse } from '../messages/Login';
import { GenericMessage as Msg } from '../messages/Message';
const router = express.Router();
@@ -25,7 +25,7 @@ router.post('/login', async (req, res) => {
sameSite: 'strict', // CSRF protection
maxAge: 86400000, // 1 day
});
- res.json(new Msg('Logged in successfully'));
+ res.json(new LoginResponse(user, await getOwnedAccounts(user)));
}catch (err) {
logger.error('Failed to authenticate:', err);
res.status(500).json(new Msg('Failed to authenticate'));
diff --git a/server/src/routes/send.ts b/server/src/routes/send.ts
index 96ad1ff..a988974 100644
--- a/server/src/routes/send.ts
+++ b/server/src/routes/send.ts
@@ -1,7 +1,7 @@
import express from 'express';
import { logger } from '../util/logging';
import { requireAuth } from '../util/auth';
-import Account from '../model/user';
+import Account, { BusinessOwnership, getOwnedAccounts } from '../model/user';
import { db } from '../util/db';
import Transaction from '../model/transaction';
import { SendRequest, SendResponse} from '../messages/Send';
@@ -12,15 +12,25 @@ const router = express.Router();
router.post('/', requireAuth, async (req, res) => {
try {
- const sender = res.locals.user as Account;
+ const user = res.locals.user as Account;
const data : SendRequest = req.body;
- const recipient = await Account.findOne({where: {id: data.recipientID}})
if ( Number(data.amount) <= 0) {
return res.status(400).json(new Err('Invalid transfer amount'));
}
+ const recipient = await Account.findOne({where: {id: data.recipientID}})
if (!recipient) {
return res.status(404).json(new Err('Recipient not found' ));
}
+ const sender = await Account.findOne({where: {id: data.senderID}})
+ if (!sender) {
+ return res.status(404).json(new Err('Sender not found' ));
+ }
+ let ownsAccount = (await getOwnedAccounts(user))?.some(
+ business => business.id == data.senderID
+ );
+ if(!(user.equals(sender) || ownsAccount )){
+ return res.status(403).json(new Err('Unauthorized sender'));
+ }
if (Number(sender.balance) < Number(data.amount)){
logger.debug(`Insufficient balance: ${sender.balance} < ${data.amount}`)
return res.status(402).json(new Err('Insufficient balance'))