From cb3b7710b9e32df408b3a38383aca049fa98214e Mon Sep 17 00:00:00 2001 From: Gunther6070 Date: Mon, 24 Mar 2025 21:17:33 -0400 Subject: Fixed various bugs and began fixing auth system. Also started implementing checkout method in cupboardService --- .../ufundapi/controller/CupboardController.java | 29 ++++++++ .../api/ufundapi/controller/UserController.java | 7 +- .../java/com/ufund/api/ufundapi/model/Need.java | 8 ++- .../java/com/ufund/api/ufundapi/model/User.java | 17 +++++ .../com/ufund/api/ufundapi/model/UserAuth.java | 7 +- .../api/ufundapi/persistence/CupboardFileDAO.java | 11 +-- .../api/ufundapi/persistence/UserAuthFIleDAO.java | 16 +++-- .../ufund/api/ufundapi/service/AuthService.java | 26 ++++--- .../api/ufundapi/service/CupboardService.java | 22 +++++- .../ufund/api/ufundapi/service/UserService.java | 6 +- .../ufundapi/persistence/CupboardFileDAOTest.java | 79 ++++++++++++++-------- .../api/ufundapi/persistence/UserFileDAOTest.java | 18 +++++ .../api/ufundapi/service/CupboardServiceTest.java | 4 +- ufund-ui/src/app/services/users.service.ts | 2 +- 14 files changed, 191 insertions(+), 61 deletions(-) diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/CupboardController.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/CupboardController.java index 36ae341..664b53b 100644 --- a/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/CupboardController.java +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/CupboardController.java @@ -13,6 +13,7 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @@ -161,6 +162,34 @@ public class CupboardController { } } + /** + * Checks out a need by checkoutAmount + * + * @param data JSON object with paramters needID and amount + * @param key Key used to authenticate user + * @return OK if successful, other statuses if failure + * @throws IllegalAccessException + */ + @PutMapping("/checkout") + public ResponseEntity checkoutNeeds(@RequestBody Map data, @RequestHeader("jelly-api-key") String key) throws IllegalAccessException { + int needID = data.get("needID"); + int checkoutAmount = data.get("amount"); + LOG.log(Level.INFO, "Checking out need with ID: " + needID + " by " + checkoutAmount); + try { + cupboardService.checkoutNeed(needID, checkoutAmount, key); + return new ResponseEntity<>(HttpStatus.OK); + } catch (IllegalArgumentException ex) { + ex.printStackTrace(); + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + } catch (IllegalAccessException ex) { + ex.printStackTrace(); + return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); + } catch (IOException ex) { + ex.printStackTrace(); + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); + } + } + /** * Deletes a single need from the cupboard using the Need's id * diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/UserController.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/UserController.java index dfaad3a..b0dbd1d 100644 --- a/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/UserController.java +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/UserController.java @@ -99,12 +99,13 @@ public class UserController { * @param key The authentication key of the user * @return OK response and the user if it was successful, or * INTERNAL_SERVER_ERROR if there was an issue + * @throws IllegalAccessException */ @PutMapping("/{username}") - public ResponseEntity updateUser(@RequestBody User user, @PathVariable String username, @RequestHeader("jelly-api-key") String key) { - LOG.log(Level.INFO,"PUT: " + user + " " + username + " " + key.toString()); + public ResponseEntity updateUser(@RequestHeader("jelly-api-key") String key, @RequestBody User user, @PathVariable String username) throws IllegalAccessException { + LOG.log(Level.INFO,"PUT: " + user + " " + username + " " + key); try { - //authService.authenticate(username, key); + authService.authenticate(username, key); user = userService.updateUser(user, username); if (user != null) { return new ResponseEntity<>(user, HttpStatus.OK); diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/model/Need.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/model/Need.java index c0e9214..22e86e3 100644 --- a/ufund-api/src/main/java/com/ufund/api/ufundapi/model/Need.java +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/model/Need.java @@ -17,14 +17,14 @@ public class Need { @JsonProperty("current") private double current; /** - * Create a new need + * Create a new need, used by the controller * * @param name The name of the need * @param id The unique ID of the need * @param maxGoal The maximum goal for this need * @param type The type of need (monetary, physical) */ - public Need(@JsonProperty("name") String name, @JsonProperty("id") int id, @JsonProperty("maxGoal") double maxGoal, GoalType type) { + public Need(@JsonProperty("name") String name, @JsonProperty("id") int id, @JsonProperty("maxGoal") double maxGoal, @JsonProperty("type") GoalType type) { this.id = id; this.name = name; this.maxGoal = maxGoal; @@ -86,6 +86,10 @@ public class Need { this.current = current; } + public void incrementCurrent(double incrementAmount) { + this.current += incrementAmount; + } + public void setFilterAttributes(String[] filterAttributes) { this.filterAttributes = filterAttributes; } diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/model/User.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/model/User.java index 6de1a8a..2871916 100644 --- a/ufund-api/src/main/java/com/ufund/api/ufundapi/model/User.java +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/model/User.java @@ -43,10 +43,21 @@ public class User { return username; } + /** + * Verifies if the provided password's hash is the same as the user's actual hash + * + * @param password The password to check if valid + * @return True or false depending on if it's equal + */ public boolean verifyPassword(String password) { return password.hashCode() == passwordHash; } + /** + * Adds a need's ID to a user's basket + * + * @param need The need to add + */ public void addToBasket(Need need) { basket.add(need.getId()); } @@ -59,6 +70,11 @@ public class User { return basket.remove(needID); } + /** + * Returns a user without a password hash for security purposes + * + * @return new User with empty password hash + */ public User withoutPasswordHash() { return new User(this.username, 0, this.basket, this.type); } @@ -71,6 +87,7 @@ public class User { this.passwordHash = other.passwordHash; } + @Override public String toString() { return this.username + "; basket: " + this.basket + "; type:" + this.type + "; hash: " + this.passwordHash; } diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/model/UserAuth.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/model/UserAuth.java index 1c11a28..78dccec 100644 --- a/ufund-api/src/main/java/com/ufund/api/ufundapi/model/UserAuth.java +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/model/UserAuth.java @@ -1,10 +1,10 @@ package com.ufund.api.ufundapi.model; -import com.fasterxml.jackson.annotation.JsonProperty; - import java.time.LocalDateTime; import java.util.UUID; +import com.fasterxml.jackson.annotation.JsonProperty; + public class UserAuth { @JsonProperty("key") String key; @JsonProperty("username") String username; @@ -12,12 +12,13 @@ public class UserAuth { public UserAuth(@JsonProperty("key") String key, @JsonProperty("username") String username, @JsonProperty("expiration") LocalDateTime expiration) { this.key = key; - this.expiration = expiration; this.username = username; + this.expiration = expiration; } /** * Generate a new user authentication profile + * * @param username the username the key will belong to * @return The new user authentication profile */ diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/CupboardFileDAO.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/CupboardFileDAO.java index 521acae..4d11554 100644 --- a/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/CupboardFileDAO.java +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/CupboardFileDAO.java @@ -1,15 +1,16 @@ package com.ufund.api.ufundapi.persistence; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.ufund.api.ufundapi.model.Need; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; - import java.io.File; import java.io.IOException; import java.util.Map; import java.util.TreeMap; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ufund.api.ufundapi.model.Need; + @Component public class CupboardFileDAO implements CupboardDAO { diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserAuthFIleDAO.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserAuthFIleDAO.java index 1fc1e92..9023b42 100644 --- a/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserAuthFIleDAO.java +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserAuthFIleDAO.java @@ -1,15 +1,17 @@ package com.ufund.api.ufundapi.persistence; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.ufund.api.ufundapi.model.UserAuth; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; - import java.io.File; import java.io.IOException; +import java.time.LocalDateTime; import java.util.HashMap; import java.util.Map; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ufund.api.ufundapi.model.UserAuth; + @Component public class UserAuthFIleDAO implements UserAuthDAO { @@ -35,7 +37,9 @@ public class UserAuthFIleDAO implements UserAuthDAO { UserAuth[] userAuthKeysArray = objectMapper.readValue(new File(filename), UserAuth[].class); for (UserAuth userAuth : userAuthKeysArray) { - userAuthMap.put(userAuth.getKey(), userAuth); + if (userAuth.getExpiration().compareTo(LocalDateTime.now()) > -1) { // Someone else double check the logic is correct. Checks if auth is valid and adds if so + userAuthMap.put(userAuth.getKey(), userAuth); + } } } diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/service/AuthService.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/service/AuthService.java index 87a16a6..71b8f41 100644 --- a/ufund-api/src/main/java/com/ufund/api/ufundapi/service/AuthService.java +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/service/AuthService.java @@ -1,11 +1,12 @@ package com.ufund.api.ufundapi.service; +import java.io.IOException; + +import org.springframework.stereotype.Component; + import com.ufund.api.ufundapi.model.User; import com.ufund.api.ufundapi.model.UserAuth; import com.ufund.api.ufundapi.persistence.UserAuthDAO; -import org.springframework.stereotype.Component; - -import java.io.IOException; @Component public class AuthService { @@ -30,12 +31,19 @@ public class AuthService { if (userAuth == null) { throw new IllegalAccessException("Unauthenticated"); } -// -// var username = userAuth.getUsername(); -// var userType = userService.getUser(username).getType(); -// if (!username.equals(targetUsername) && userType != User.UserType.MANAGER) { -// throw new IllegalAccessException("Unauthorized"); -// } + + var username = userAuth.getUsername(); + var userType = userService.getUser(username).getType(); + if (!username.equals(targetUsername) && userType != User.UserType.MANAGER) { + throw new IllegalAccessException("Unauthorized"); + } + } + + public void authenticate(String key) throws IOException, IllegalAccessException { + var userAuth = userAuthDAO.getUserAuth(key); + if (userAuth == null) { + throw new IllegalAccessException("Unauthenticated"); + } } /** diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/service/CupboardService.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/service/CupboardService.java index 2398745..8713882 100644 --- a/ufund-api/src/main/java/com/ufund/api/ufundapi/service/CupboardService.java +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/service/CupboardService.java @@ -3,6 +3,7 @@ package com.ufund.api.ufundapi.service; import java.io.IOException; import java.util.Arrays; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; import com.ufund.api.ufundapi.DuplicateKeyException; @@ -13,8 +14,10 @@ import com.ufund.api.ufundapi.persistence.CupboardDAO; public class CupboardService { private final CupboardDAO cupboardDAO; + final AuthService authService; - public CupboardService(CupboardDAO cupboardDAO) { + public CupboardService(@Lazy AuthService authService, CupboardDAO cupboardDAO) { + this.authService = authService; this.cupboardDAO = cupboardDAO; } @@ -96,6 +99,23 @@ public class CupboardService { return cupboardDAO.updateNeed(need); } + /** + * Checks out a need with the desired amount + * + * @param id The ID of the need to update + * @param checkoutAmount The amount to update the need by + * @throws IOException + * @throws IllegalAccessException + */ + public void checkoutNeed(int id, double checkoutAmount, String key) throws IOException, IllegalAccessException { + if (checkoutAmount <= 0) { + throw new IllegalArgumentException("Amount must be greather than 0"); + } + authService.authenticate(key); + Need need = cupboardDAO.getNeed(id); + need.incrementCurrent(checkoutAmount); + } + /** * Delete a need from the cupboard * diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/service/UserService.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/service/UserService.java index caf9f4c..aaa2f06 100644 --- a/ufund-api/src/main/java/com/ufund/api/ufundapi/service/UserService.java +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/service/UserService.java @@ -2,6 +2,7 @@ package com.ufund.api.ufundapi.service; import java.io.IOException; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; import com.ufund.api.ufundapi.DuplicateKeyException; @@ -12,7 +13,7 @@ import com.ufund.api.ufundapi.persistence.UserDAO; public class UserService { private final UserDAO userDAO; - private final CupboardService cupboardService; + final CupboardService cupboardService; public UserService(UserDAO userDao, CupboardService cupboardService) { this.userDAO = userDao; @@ -44,6 +45,9 @@ public class UserService { */ public User getUser(String username) throws IOException { User user = userDAO.getUser(username); + if (user == null) { + return null; + } for (int needId : user.getNeeds()) { if (cupboardService.getNeed(needId) == null) { user.removeBasketNeed(needId); diff --git a/ufund-api/src/test/java/com/ufund/api/ufundapi/persistence/CupboardFileDAOTest.java b/ufund-api/src/test/java/com/ufund/api/ufundapi/persistence/CupboardFileDAOTest.java index f786a8c..0ebbeca 100644 --- a/ufund-api/src/test/java/com/ufund/api/ufundapi/persistence/CupboardFileDAOTest.java +++ b/ufund-api/src/test/java/com/ufund/api/ufundapi/persistence/CupboardFileDAOTest.java @@ -4,6 +4,7 @@ import java.io.File; import java.io.IOException; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; @@ -20,44 +21,43 @@ import com.ufund.api.ufundapi.model.Need.GoalType; @Tag("Persistence-tier") public class CupboardFileDAOTest { - private CupboardFileDAO cupboardFileDao; - private Need[] testNeeds; - private ObjectMapper mockObjectMapper; - - @BeforeEach - public void setupCupboardFileDao() throws IOException { - mockObjectMapper = mock(ObjectMapper.class); - testNeeds = new Need[]{ - new Need("one", 0, 100, Need.GoalType.MONETARY), - new Need("two", 1, 100, Need.GoalType.MONETARY), - new Need("three", 2, 100, Need.GoalType.MONETARY) + private CupboardFileDAO cupboardFileDao; + private Need[] testNeeds; + private ObjectMapper mockObjectMapper; + + @BeforeEach + public void setupCupboardFileDao() throws IOException { + mockObjectMapper = mock(ObjectMapper.class); + testNeeds = new Need[] { + new Need("one", 0, 100, Need.GoalType.MONETARY), + new Need("two", 1, 100, Need.GoalType.MONETARY), + new Need("three", 2, 100, Need.GoalType.MONETARY) }; - // When the object mapper is supposed to read from the file - // the mock object mapper will return the hero array above - when(mockObjectMapper - .readValue(new File("doesnt_matter.txt"),Need[].class)) - .thenReturn(testNeeds); - cupboardFileDao = new CupboardFileDAO("doesnt_matter.txt",mockObjectMapper); - } - - @Test - public void getNeedsTest() { - Need[] needs = cupboardFileDao.getNeeds(); - assertEquals(needs.length,testNeeds.length); + // When the object mapper is supposed to read from the file + // the mock object mapper will return the hero array above + when(mockObjectMapper + .readValue(new File("doesnt_matter.txt"), Need[].class)) + .thenReturn(testNeeds); + cupboardFileDao = new CupboardFileDAO("doesnt_matter.txt", mockObjectMapper); + } + + @Test + public void getNeedsTest() { + Need[] needs = cupboardFileDao.getNeeds(); + assertEquals(needs.length, testNeeds.length); assertEquals(needs[0].getName(), testNeeds[0].getName()); - } + } - @Test - public void getNeedTest() { + @Test + public void getNeedTest() { Need need1 = cupboardFileDao.getNeed(0); - + assertEquals(testNeeds[0], need1); - } + } @Test public void createNeedTest() throws IOException { Need newNeed = new Need("sea urchin hats", 3, 100, GoalType.PHYSICAL); - Need actualNeed = cupboardFileDao.addNeed(newNeed); @@ -78,6 +78,15 @@ public class CupboardFileDAOTest { assertNull(deletedNeed); } + @Test + public void deleteNeedTestFail() throws IOException { + Need undeletedNeed = cupboardFileDao.getNeed(0); + assertNotNull(undeletedNeed); + + boolean nullNeed = cupboardFileDao.deleteNeed(20); + assertFalse(nullNeed); + } + @Test public void updateNeedTest() throws IOException { Need[] needs = cupboardFileDao.getNeeds(); @@ -91,4 +100,16 @@ public class CupboardFileDAOTest { assertNotEquals(actualNeed, unupdatedNeed); } + @Test + public void updateNeedTestFail() throws IOException { + Need[] needs = cupboardFileDao.getNeeds(); + Need unupdatedNeed = needs[needs.length - 1]; + assertNotNull(unupdatedNeed); + + Need updatedNeed = new Need("sequin sea urchin hats", 20, 100, GoalType.PHYSICAL); + + Need actualNeed = cupboardFileDao.updateNeed(updatedNeed); + assertNull(actualNeed); + } + } diff --git a/ufund-api/src/test/java/com/ufund/api/ufundapi/persistence/UserFileDAOTest.java b/ufund-api/src/test/java/com/ufund/api/ufundapi/persistence/UserFileDAOTest.java index 9361188..2ee0fc0 100644 --- a/ufund-api/src/test/java/com/ufund/api/ufundapi/persistence/UserFileDAOTest.java +++ b/ufund-api/src/test/java/com/ufund/api/ufundapi/persistence/UserFileDAOTest.java @@ -39,6 +39,24 @@ public class UserFileDAOTest { userFileDAO = new UserFileDAO("doesnt_matter.txt",mockObjectMapper); } + @Test + public void addUsersTest() throws IOException { + User user = User.create("Name", "Pass"); + + User addedUser = userFileDAO.addUser(user); + + assertEquals(addedUser, user); + } + + @Test + public void addUsersTestFail() throws IOException { + User user = User.create("bob", "test"); + + User existingUser = userFileDAO.addUser(user); + + assertEquals(existingUser, testUsers[0]); + } + @Test public void getUsersTest() { User[] users = userFileDAO.getUsers(); diff --git a/ufund-api/src/test/java/com/ufund/api/ufundapi/service/CupboardServiceTest.java b/ufund-api/src/test/java/com/ufund/api/ufundapi/service/CupboardServiceTest.java index 99ca23c..59f5b1b 100644 --- a/ufund-api/src/test/java/com/ufund/api/ufundapi/service/CupboardServiceTest.java +++ b/ufund-api/src/test/java/com/ufund/api/ufundapi/service/CupboardServiceTest.java @@ -23,11 +23,13 @@ public class CupboardServiceTest { private CupboardDAO mockCupboardDAO; private CupboardService cupboardService; + private AuthService mockAuthService; @BeforeEach public void setupCupboardService() { mockCupboardDAO = mock(CupboardDAO.class); - cupboardService = new CupboardService(mockCupboardDAO); + mockAuthService = mock(AuthService.class); + cupboardService = new CupboardService(mockAuthService, mockCupboardDAO); } diff --git a/ufund-ui/src/app/services/users.service.ts b/ufund-ui/src/app/services/users.service.ts index dba8185..6709192 100644 --- a/ufund-ui/src/app/services/users.service.ts +++ b/ufund-ui/src/app/services/users.service.ts @@ -45,7 +45,7 @@ export class UsersService { } updateUser(user: User): Observable { - return this.http.put(`${this.url}/${user.username}`,user, this.httpOptions2) + return this.http.put(`${this.url}/${user.username}`, user, this.httpOptions2) } deleteUser(id: number): Observable { -- cgit v1.2.3 From 12551843966b285ce3113fe0243626cc961a7715 Mon Sep 17 00:00:00 2001 From: Gunther6070 Date: Mon, 24 Mar 2025 21:18:25 -0400 Subject: Added comment --- ufund-ui/src/app/services/users.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ufund-ui/src/app/services/users.service.ts b/ufund-ui/src/app/services/users.service.ts index 6709192..8515073 100644 --- a/ufund-ui/src/app/services/users.service.ts +++ b/ufund-ui/src/app/services/users.service.ts @@ -45,7 +45,7 @@ export class UsersService { } updateUser(user: User): Observable { - return this.http.put(`${this.url}/${user.username}`, user, this.httpOptions2) + return this.http.put(`${this.url}/${user.username}`, user, this.httpOptions2) // This line is causing issues as the key is not properly being passed } deleteUser(id: number): Observable { -- cgit v1.2.3 From a8175ba69669fddadfbe143e11972cc21821ed5f Mon Sep 17 00:00:00 2001 From: sowgro Date: Mon, 24 Mar 2025 22:02:07 -0400 Subject: Fix authentication bug --- ufund-ui/src/app/services/users.service.ts | 35 +++++++++++++++++------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/ufund-ui/src/app/services/users.service.ts b/ufund-ui/src/app/services/users.service.ts index 8515073..6671440 100644 --- a/ufund-ui/src/app/services/users.service.ts +++ b/ufund-ui/src/app/services/users.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import {HttpClient, HttpHeaders} from '@angular/common/http'; -import {BehaviorSubject, firstValueFrom, Observable} from 'rxjs'; +import {BehaviorSubject, catchError, firstValueFrom, Observable, of} from 'rxjs'; import {User} from '../models/User'; import { Need } from '../models/Need'; import { CupboardService } from './cupboard.service'; @@ -16,20 +16,20 @@ export class UsersService { private url = "http://localhost:8080/users" private authUrl = "http://localhost:8080/auth" - private httpOptions = { + private httpOptions = () => ({ headers: new HttpHeaders({ 'Content-Type': 'application/json', "jelly-api-key": this.apiKey }) - }; - private httpOptions2 = { + }); + private httpOptions2 = () => ({ headers: new HttpHeaders({ 'Content-Type': 'application/json', "jelly-api-key": this.apiKey }), responseType: "text" as "json" // don't ask me how or why this works, bc i have no clue... // see the relevant angular bug report https://github.com/angular/angular/issues/18586 - }; + }); constructor( private http: HttpClient, @@ -37,19 +37,20 @@ export class UsersService { ) {} async createUser(username:string, password:string) { - await firstValueFrom(this.http.post(this.url, {username: username, password: password}, this.httpOptions)) + await firstValueFrom(this.http.post(this.url, {username: username, password: password}, this.httpOptions())) } getUser(id: string): Observable { - return this.http.get(`${this.url}/${id}`, this.httpOptions) + return this.http.get(`${this.url}/${id}`, this.httpOptions()) } updateUser(user: User): Observable { - return this.http.put(`${this.url}/${user.username}`, user, this.httpOptions2) // This line is causing issues as the key is not properly being passed + console.log(`${this.url}/${user.username}`, user, this.httpOptions) + return this.http.put(`${this.url}/${user.username}`, user, this.httpOptions2()) // This line is causing issues as the key is not properly being passed } deleteUser(id: number): Observable { - return this.http.delete(`${this.url}/${id}`, this.httpOptions) + return this.http.delete(`${this.url}/${id}`, this.httpOptions()) } getCurrentUserSubject() { @@ -61,7 +62,7 @@ export class UsersService { } async login(username: string, password: string) { - let res = this.http.post(this.authUrl, {username: username, password: password}, this.httpOptions2); + let res = this.http.post(this.authUrl, {username: username, password: password}, this.httpOptions2()); this.apiKey = await firstValueFrom(res); console.log("apikey: "+this.apiKey) let res2 = this.http.get(`${this.url}/${username}`, { @@ -81,16 +82,20 @@ export class UsersService { }) Promise.all(promiseArr).then(r => this.basket.next(r)); } - + removeNeed(id: number) { let newArr = this.basket.getValue().filter(v => v.id != id); this.basket.next(newArr); this.getCurrentUser()!.basket = newArr.map(need => need.id); - this.updateUser(this.getCurrentUser()!).subscribe(() => { + this.updateUser(this.getCurrentUser()!) + .pipe( + catchError((err: any, ob) => { + console.error(err); + return of(); + }) + ) + .subscribe(() => { this.refreshBasket(); - error: (err: any) => { - console.error(err); - } }); } -- cgit v1.2.3 From c15aa3daab0cf9a640945d4e634d1327fb55d2db Mon Sep 17 00:00:00 2001 From: sowgro Date: Tue, 25 Mar 2025 00:03:45 -0400 Subject: Greatly improve logging and other backend clean up --- .../api/ufundapi/controller/AuthController.java | 12 ++++- .../ufundapi/controller/CupboardController.java | 57 +++++++++++++--------- .../api/ufundapi/controller/UserController.java | 38 +++++++++------ .../java/com/ufund/api/ufundapi/model/Need.java | 2 +- .../api/ufundapi/persistence/CupboardFileDAO.java | 13 +---- .../api/ufundapi/persistence/UserFileDAO.java | 9 ++-- .../ufund/api/ufundapi/service/AuthService.java | 8 +-- .../api/ufundapi/service/CupboardService.java | 6 +-- 8 files changed, 81 insertions(+), 64 deletions(-) diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/AuthController.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/AuthController.java index b46d4ee..6ba6160 100644 --- a/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/AuthController.java +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/AuthController.java @@ -2,6 +2,8 @@ package com.ufund.api.ufundapi.controller; import java.io.IOException; import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -17,6 +19,7 @@ import com.ufund.api.ufundapi.service.AuthService; @RestController @RequestMapping("auth") public class AuthController { + private static final Logger LOG = Logger.getLogger(AuthController.class.getName()); private final AuthService authService; public AuthController(AuthService authService) { @@ -32,14 +35,17 @@ public class AuthController { */ @PostMapping("") public ResponseEntity login(@RequestBody Map params) { + LOG.log(Level.INFO, "POST /auth body: {0}", params); String username = params.get("username"); String password = params.get("password"); try { String key = authService.login(username, password); return new ResponseEntity<>(key, HttpStatus.OK); - } catch (IllegalAccessException e) { + } catch (IllegalAccessException ex) { + LOG.log(Level.WARNING, ex.getLocalizedMessage()); return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); } catch (IOException ex) { + LOG.log(Level.SEVERE, ex.getLocalizedMessage()); return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } } @@ -52,10 +58,12 @@ public class AuthController { */ @DeleteMapping("") public ResponseEntity logout(@RequestHeader("jelly-api-key") String key) { + LOG.log(Level.INFO, "DELETE /auth key: {0}", key); try { authService.logout(key); return new ResponseEntity<>(HttpStatus.OK); - } catch (IOException e) { + } catch (IOException ex) { + LOG.log(Level.WARNING, ex.getLocalizedMessage()); return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } } diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/CupboardController.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/CupboardController.java index 664b53b..8db8901 100644 --- a/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/CupboardController.java +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/CupboardController.java @@ -23,6 +23,8 @@ import com.ufund.api.ufundapi.model.Need; import com.ufund.api.ufundapi.model.Need.GoalType; import com.ufund.api.ufundapi.service.CupboardService; +import static java.util.List.of; + @RestController @RequestMapping("cupboard") public class CupboardController { @@ -49,7 +51,8 @@ public class CupboardController { */ @PostMapping("") public ResponseEntity createNeed(@RequestBody Map params) { - System.out.println(params); + LOG.log(Level.INFO, "POST /cupboard body: {0}", params); + String name = (String) params.get("name"); double maxGoal = (double) params.get("maxGoal"); Need.GoalType goalType = GoalType.valueOf((String) params.get("type")); @@ -58,10 +61,13 @@ public class CupboardController { Need need = cupboardService.createNeed(name, maxGoal, goalType); return new ResponseEntity<>(need, HttpStatus.OK); } catch (DuplicateKeyException ex) { + LOG.log(Level.WARNING, ex.getLocalizedMessage()); return new ResponseEntity<>(HttpStatus.CONFLICT); } catch (IllegalArgumentException ex) { + LOG.log(Level.WARNING, ex.getLocalizedMessage()); return new ResponseEntity<>(HttpStatus.BAD_REQUEST); } catch (IOException ex) { + LOG.log(Level.SEVERE, ex.getLocalizedMessage()); return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } } @@ -76,7 +82,7 @@ public class CupboardController { */ @GetMapping("") public ResponseEntity getNeeds() { - LOG.info("GET /needs"); + LOG.info("GET /cupboard"); try { Need[] needs = cupboardService.getNeeds(); @@ -88,19 +94,21 @@ public class CupboardController { } /** - * Responds to the GET request for all {@linkplain Need need} whose name contains - * the text in name - * - * @param name The name parameter which contains the text used to find the {@link Need need} - * - * @return ResponseEntity with array of {@link Need need} objects (may be empty) and - * HTTP status of OK
- * ResponseEntity with HTTP status of INTERNAL_SERVER_ERROR otherwise - *

- */ + * Responds to the GET request for all {@linkplain Need need} whose name contains + * the text in name + * + * @param name The name parameter which contains the text used to find the {@link Need need} + * + * @deprecated Searching should now be done client side in the future + * + * @return ResponseEntity with array of {@link Need need} objects (may be empty) and + * HTTP status of OK
+ * ResponseEntity with HTTP status of INTERNAL_SERVER_ERROR otherwise + *

+ */ @GetMapping("/") public ResponseEntity searchNeeds(@RequestParam String name) { - LOG.info("GET /need/?name="+name); + LOG.info("GET /cupboard/?name="+name); try { Need[] needs = cupboardService.searchNeeds(name); @@ -121,7 +129,7 @@ public class CupboardController { */ @GetMapping("/{id}") public ResponseEntity getNeed(@PathVariable int id) { - LOG.log(Level.INFO, "GET /need/{0}", id); + LOG.log(Level.INFO, "GET /cupboard/{0}", id); try { Need need = cupboardService.getNeed(id); @@ -145,7 +153,7 @@ public class CupboardController { */ @PutMapping("/{id}") public ResponseEntity updateNeed(@RequestBody Need need, @PathVariable int id) { - LOG.log(Level.INFO, "Updating need: " + need); + LOG.log(Level.INFO, "PUT /cupboard/{0} body: {1}", of(id, need)); try { Need updatedNeed = cupboardService.updateNeed(need, id); if (updatedNeed != null) { @@ -154,10 +162,10 @@ public class CupboardController { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } } catch (IllegalArgumentException ex) { - ex.printStackTrace(); + LOG.log(Level.WARNING, ex.getLocalizedMessage()); return new ResponseEntity<>(HttpStatus.BAD_REQUEST); } catch (IOException ex) { - ex.printStackTrace(); + LOG.log(Level.SEVERE, ex.getLocalizedMessage()); return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } } @@ -168,24 +176,23 @@ public class CupboardController { * @param data JSON object with paramters needID and amount * @param key Key used to authenticate user * @return OK if successful, other statuses if failure - * @throws IllegalAccessException */ @PutMapping("/checkout") - public ResponseEntity checkoutNeeds(@RequestBody Map data, @RequestHeader("jelly-api-key") String key) throws IllegalAccessException { + public ResponseEntity checkoutNeeds(@RequestBody Map data, @RequestHeader("jelly-api-key") String key) { int needID = data.get("needID"); int checkoutAmount = data.get("amount"); - LOG.log(Level.INFO, "Checking out need with ID: " + needID + " by " + checkoutAmount); + LOG.log(Level.INFO, "PUT /need/checkout body: {0}", data); try { cupboardService.checkoutNeed(needID, checkoutAmount, key); return new ResponseEntity<>(HttpStatus.OK); } catch (IllegalArgumentException ex) { - ex.printStackTrace(); + LOG.log(Level.WARNING, ex.getLocalizedMessage()); return new ResponseEntity<>(HttpStatus.BAD_REQUEST); } catch (IllegalAccessException ex) { - ex.printStackTrace(); + LOG.log(Level.WARNING, ex.getLocalizedMessage()); return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); } catch (IOException ex) { - ex.printStackTrace(); + LOG.log(Level.SEVERE, ex.getLocalizedMessage()); return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } } @@ -198,6 +205,7 @@ public class CupboardController { */ @DeleteMapping("/{id}") public ResponseEntity deleteNeed(@PathVariable int id) { + LOG.log(Level.INFO, "DELETE /cupboard/{0}", id); try { Need need = cupboardService.getNeed(id); if (cupboardService.deleteNeed(id)) { @@ -205,7 +213,8 @@ public class CupboardController { } else { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } - } catch (IOException e) { + } catch (IOException ex) { + LOG.log(Level.SEVERE, ex.getLocalizedMessage()); return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } } diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/UserController.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/UserController.java index b0dbd1d..cd340ef 100644 --- a/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/UserController.java +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/UserController.java @@ -23,6 +23,8 @@ import com.ufund.api.ufundapi.model.User; import com.ufund.api.ufundapi.service.AuthService; import com.ufund.api.ufundapi.service.UserService; +import static java.util.List.of; + @RestController @RequestMapping("users") public class UserController { @@ -43,6 +45,7 @@ public class UserController { */ @PostMapping("") public ResponseEntity createUser(@RequestBody Map params) { + LOG.log(Level.INFO, "POST /users body: {0}", params); String username = params.get("username"); String password = params.get("password"); @@ -54,8 +57,10 @@ public class UserController { return new ResponseEntity<>(HttpStatus.CONFLICT); } } catch (DuplicateKeyException ex) { + LOG.log(Level.WARNING, ex.getLocalizedMessage()); return new ResponseEntity<>(HttpStatus.CONFLICT); } catch (IOException ex) { + LOG.log(Level.SEVERE, ex.getLocalizedMessage()); return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } } @@ -72,7 +77,7 @@ public class UserController { */ @GetMapping("/{username}") public ResponseEntity getUser(@PathVariable String username, @RequestHeader("jelly-api-key") String key) { - LOG.log(Level.INFO, "GET /user/{0}", username); + LOG.log(Level.INFO, "GET /user/{0} key: {1}", of(username, key)); try { authService.authenticate(username, key); @@ -83,9 +88,10 @@ public class UserController { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } } catch (IllegalAccessException ex) { + LOG.log(Level.WARNING, ex.getLocalizedMessage()); return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); - } catch (IOException e) { - LOG.log(Level.SEVERE, e.getLocalizedMessage()); + } catch (IOException ex) { + LOG.log(Level.SEVERE, ex.getLocalizedMessage()); return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } @@ -99,11 +105,10 @@ public class UserController { * @param key The authentication key of the user * @return OK response and the user if it was successful, or * INTERNAL_SERVER_ERROR if there was an issue - * @throws IllegalAccessException */ @PutMapping("/{username}") - public ResponseEntity updateUser(@RequestHeader("jelly-api-key") String key, @RequestBody User user, @PathVariable String username) throws IllegalAccessException { - LOG.log(Level.INFO,"PUT: " + user + " " + username + " " + key); + public ResponseEntity updateUser(@RequestBody User user, @PathVariable String username, @RequestHeader("jelly-api-key") String key) { + LOG.log(Level.INFO,"PUT /users/{0} body: {1} key: {2}", of(user, username, key)); try { authService.authenticate(username, key); user = userService.updateUser(user, username); @@ -113,13 +118,15 @@ public class UserController { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } } catch (InvalidParameterException ex) { + LOG.log(Level.WARNING, ex.getLocalizedMessage()); return new ResponseEntity<>(HttpStatus.BAD_REQUEST); - } catch (IOException e) { + } catch (IllegalAccessException ex) { + LOG.log(Level.WARNING, ex.getLocalizedMessage()); + return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); + } catch (IOException ex) { + LOG.log(Level.SEVERE, ex.getLocalizedMessage()); return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); - } - // catch (IllegalAccessException e) { - // return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); - // } + } } /** @@ -132,6 +139,7 @@ public class UserController { */ @DeleteMapping("/{username}") public ResponseEntity deleteUser(@PathVariable String username, @RequestHeader("jelly-api-key") String key) { + LOG.log(Level.INFO, "DELETE /users/{0} id: {1}", of(username, key)); try { authService.authenticate(username, key); @@ -140,10 +148,12 @@ public class UserController { } else { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } - } catch (IOException e) { - return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); - } catch (IllegalAccessException e) { + } catch (IllegalAccessException ex) { + LOG.log(Level.WARNING, ex.getLocalizedMessage()); return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); + } catch (IOException ex) { + LOG.log(Level.SEVERE, ex.getLocalizedMessage()); + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } } diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/model/Need.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/model/Need.java index 22e86e3..786b104 100644 --- a/ufund-api/src/main/java/com/ufund/api/ufundapi/model/Need.java +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/model/Need.java @@ -38,7 +38,7 @@ public class Need { * @param maxGoal The maximum goal for this need * @param type The type of need (monetary, physical) */ - public Need(String name, GoalType type, double maxGoal) { + public Need(String name, GoalType type, double maxGoal) { // TODO why is this needed this.name = name; this.type = type; this.maxGoal = maxGoal; diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/CupboardFileDAO.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/CupboardFileDAO.java index 4d11554..3115204 100644 --- a/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/CupboardFileDAO.java +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/CupboardFileDAO.java @@ -52,15 +52,6 @@ public class CupboardFileDAO implements CupboardDAO { nextId++; } - /** - * Return an array of the needs - * - * @return An array of all the needs - */ - private Need[] getNeedsArray() { - return needs.values().toArray(Need[]::new); - } - /** * Saves the needs to json * @@ -68,7 +59,7 @@ public class CupboardFileDAO implements CupboardDAO { * @throws IOException If there was an IO issue saving the file */ private boolean save() throws IOException { - Need[] needArray = getNeedsArray(); + Need[] needArray = needs.values().toArray(Need[]::new); objectMapper.writeValue(new File(filename), needArray); return true; @@ -77,7 +68,7 @@ public class CupboardFileDAO implements CupboardDAO { @Override public Need[] getNeeds() { synchronized (needs) { - return getNeedsArray(); + return needs.values().toArray(Need[]::new); } } diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserFileDAO.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserFileDAO.java index 6e900aa..1b888cd 100644 --- a/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserFileDAO.java +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserFileDAO.java @@ -5,6 +5,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; +import java.util.Objects; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @@ -82,16 +83,14 @@ public class UserFileDAO implements UserDAO { public User updateUser(User user) throws IOException { synchronized (users) { if (users.containsKey(user.getUsername())) { - // var old = users.put(user.getUsername(), user); - // user.copyPassword(old); - if (user.getNeeds() == null || user.getType() == null) { + if (user.getNeeds() == null || user.getType() == null) { // TODO clean this up -tyler User oldData = users.get(user.getUsername()); - User crutch = new User(oldData.getUsername(), 0, new ArrayList(), oldData.getType()); + User crutch = new User(oldData.getUsername(), 0, new ArrayList<>(), oldData.getType()); crutch.copyPassword(oldData); users.put(user.getUsername(), crutch); } else { var old = users.put(user.getUsername(), user); - user.copyPassword(old); + user.copyPassword(Objects.requireNonNull(old)); } save(); return user; diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/service/AuthService.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/service/AuthService.java index 71b8f41..4e5ebce 100644 --- a/ufund-api/src/main/java/com/ufund/api/ufundapi/service/AuthService.java +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/service/AuthService.java @@ -29,20 +29,20 @@ public class AuthService { public void authenticate(String targetUsername, String key) throws IllegalAccessException, IOException { var userAuth = userAuthDAO.getUserAuth(key); if (userAuth == null) { - throw new IllegalAccessException("Unauthenticated"); + throw new IllegalAccessException("Invalid authentication key"); } var username = userAuth.getUsername(); var userType = userService.getUser(username).getType(); if (!username.equals(targetUsername) && userType != User.UserType.MANAGER) { - throw new IllegalAccessException("Unauthorized"); + throw new IllegalAccessException("Provided key does not grant access to perform the requested operation"); } } public void authenticate(String key) throws IOException, IllegalAccessException { var userAuth = userAuthDAO.getUserAuth(key); if (userAuth == null) { - throw new IllegalAccessException("Unauthenticated"); + throw new IllegalAccessException("Invalid authentication key"); } } @@ -58,7 +58,7 @@ public class AuthService { public String login(String username, String password) throws IllegalAccessException, IOException { var usr = userService.getUser(username); if (usr == null || !usr.verifyPassword(password)) { - throw new IllegalAccessException("Unauthorized"); + throw new IllegalAccessException("Incorrect username or password"); } var userAuth = UserAuth.generate(username); userAuthDAO.addUserAuth(userAuth); diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/service/CupboardService.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/service/CupboardService.java index 8713882..91e3ba5 100644 --- a/ufund-api/src/main/java/com/ufund/api/ufundapi/service/CupboardService.java +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/service/CupboardService.java @@ -104,12 +104,12 @@ public class CupboardService { * * @param id The ID of the need to update * @param checkoutAmount The amount to update the need by - * @throws IOException - * @throws IllegalAccessException + * @throws IOException If there is an error reading the file + * @throws IllegalAccessException If the user has insufficient permission */ public void checkoutNeed(int id, double checkoutAmount, String key) throws IOException, IllegalAccessException { if (checkoutAmount <= 0) { - throw new IllegalArgumentException("Amount must be greather than 0"); + throw new IllegalArgumentException("Amount must be greater than 0"); } authService.authenticate(key); Need need = cupboardDAO.getNeed(id); -- cgit v1.2.3 From d31c1aec7f615646553a227c8e235d4ae2679c68 Mon Sep 17 00:00:00 2001 From: Gunther6070 Date: Tue, 25 Mar 2025 08:20:31 -0400 Subject: Rename user getNeeds to getBasket --- .../java/com/ufund/api/ufundapi/model/User.java | 27 +++++++++++++--------- .../api/ufundapi/persistence/UserFileDAO.java | 2 +- .../ufund/api/ufundapi/service/UserService.java | 6 ++--- .../com/ufund/api/ufundapi/model/UserTest.java | 4 ++-- 4 files changed, 22 insertions(+), 17 deletions(-) diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/model/User.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/model/User.java index 2871916..d04d8b7 100644 --- a/ufund-api/src/main/java/com/ufund/api/ufundapi/model/User.java +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/model/User.java @@ -12,18 +12,23 @@ public class User { MANAGER } - @JsonProperty("username") private final String username; - @JsonProperty("passwordHash") private int passwordHash; - @JsonProperty("basket") private final List basket; - @JsonProperty("type") private final UserType type; + @JsonProperty("username") + private final String username; + @JsonProperty("passwordHash") + private int passwordHash; + @JsonProperty("basket") + private final List basket; + @JsonProperty("type") + private final UserType type; /** * Create a new user * - * @param username The name of the user - * @param basket A basket to copy from + * @param username The name of the user + * @param basket A basket to copy from */ - public User(@JsonProperty("username") String username, @JsonProperty("passwordHash") int passwordHash, @JsonProperty("basket") List basket, @JsonProperty("type") UserType userType) { + public User(@JsonProperty("username") String username, @JsonProperty("passwordHash") int passwordHash, + @JsonProperty("basket") List basket, @JsonProperty("type") UserType userType) { this.username = username; this.basket = basket; this.passwordHash = passwordHash; @@ -35,8 +40,7 @@ public class User { username, password.hashCode(), new ArrayList<>(), - UserType.HELPER - ); + UserType.HELPER); } public String getUsername() { @@ -44,7 +48,8 @@ public class User { } /** - * Verifies if the provided password's hash is the same as the user's actual hash + * Verifies if the provided password's hash is the same as the user's actual + * hash * * @param password The password to check if valid * @return True or false depending on if it's equal @@ -62,7 +67,7 @@ public class User { basket.add(need.getId()); } - public Integer[] getNeeds() { + public Integer[] getBasket() { return basket.toArray(Integer[]::new); } diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserFileDAO.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserFileDAO.java index 6e900aa..16560e7 100644 --- a/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserFileDAO.java +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserFileDAO.java @@ -84,7 +84,7 @@ public class UserFileDAO implements UserDAO { if (users.containsKey(user.getUsername())) { // var old = users.put(user.getUsername(), user); // user.copyPassword(old); - if (user.getNeeds() == null || user.getType() == null) { + if (user.getBasket() == null || user.getType() == null) { User oldData = users.get(user.getUsername()); User crutch = new User(oldData.getUsername(), 0, new ArrayList(), oldData.getType()); crutch.copyPassword(oldData); diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/service/UserService.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/service/UserService.java index aaa2f06..51283fc 100644 --- a/ufund-api/src/main/java/com/ufund/api/ufundapi/service/UserService.java +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/service/UserService.java @@ -48,7 +48,7 @@ public class UserService { if (user == null) { return null; } - for (int needId : user.getNeeds()) { + for (int needId : user.getBasket()) { if (cupboardService.getNeed(needId) == null) { user.removeBasketNeed(needId); } @@ -59,7 +59,7 @@ public class UserService { /** * Updates a user * - * @param user The ID of the user to update + * @param user The ID of the user to update * @param username The user object to set (note: the ID is ignored) * @return The updated user object * @throws IOException Thrown if there was any issue saving the data @@ -81,5 +81,5 @@ public class UserService { public boolean deleteUser(String username) throws IOException { return userDAO.deleteUser(username); } - + } diff --git a/ufund-api/src/test/java/com/ufund/api/ufundapi/model/UserTest.java b/ufund-api/src/test/java/com/ufund/api/ufundapi/model/UserTest.java index 55b7f07..517a7e2 100644 --- a/ufund-api/src/test/java/com/ufund/api/ufundapi/model/UserTest.java +++ b/ufund-api/src/test/java/com/ufund/api/ufundapi/model/UserTest.java @@ -59,7 +59,7 @@ public class UserTest { user.addToBasket(need); - Need getNeed = cupboardService.getNeed(user.getNeeds()[0]); + Need getNeed = cupboardService.getNeed(user.getBasket()[0]); assertEquals(needs[0], getNeed); @@ -80,7 +80,7 @@ public class UserTest { user.removeBasketNeed(need.getId()); user.addToBasket(need2); - Need getNeed = cupboardService.getNeed(user.getNeeds()[0]); + Need getNeed = cupboardService.getNeed(user.getBasket()[0]); assertEquals(need2, getNeed); -- cgit v1.2.3 From a2f35f6c35b96e3103d8eb6c2bdefc7c081f72f2 Mon Sep 17 00:00:00 2001 From: sowgro Date: Tue, 25 Mar 2025 09:05:23 -0400 Subject: Tweak logging --- .../java/com/ufund/api/ufundapi/controller/AuthController.java | 4 ++-- .../com/ufund/api/ufundapi/controller/CupboardController.java | 6 +++--- .../java/com/ufund/api/ufundapi/controller/UserController.java | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/AuthController.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/AuthController.java index 6ba6160..aa99a90 100644 --- a/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/AuthController.java +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/AuthController.java @@ -35,7 +35,7 @@ public class AuthController { */ @PostMapping("") public ResponseEntity login(@RequestBody Map params) { - LOG.log(Level.INFO, "POST /auth body: {0}", params); + LOG.log(Level.INFO, "POST /auth body={0}", params); String username = params.get("username"); String password = params.get("password"); try { @@ -58,7 +58,7 @@ public class AuthController { */ @DeleteMapping("") public ResponseEntity logout(@RequestHeader("jelly-api-key") String key) { - LOG.log(Level.INFO, "DELETE /auth key: {0}", key); + LOG.log(Level.INFO, "DELETE /auth key={0}", key); try { authService.logout(key); return new ResponseEntity<>(HttpStatus.OK); diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/CupboardController.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/CupboardController.java index 8db8901..e62d5ab 100644 --- a/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/CupboardController.java +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/CupboardController.java @@ -51,7 +51,7 @@ public class CupboardController { */ @PostMapping("") public ResponseEntity createNeed(@RequestBody Map params) { - LOG.log(Level.INFO, "POST /cupboard body: {0}", params); + LOG.log(Level.INFO, "POST /cupboard body={0}", params); String name = (String) params.get("name"); double maxGoal = (double) params.get("maxGoal"); @@ -153,7 +153,7 @@ public class CupboardController { */ @PutMapping("/{id}") public ResponseEntity updateNeed(@RequestBody Need need, @PathVariable int id) { - LOG.log(Level.INFO, "PUT /cupboard/{0} body: {1}", of(id, need)); + LOG.log(Level.INFO, "PUT /cupboard/{0} body={1}", of(id, need)); try { Need updatedNeed = cupboardService.updateNeed(need, id); if (updatedNeed != null) { @@ -181,7 +181,7 @@ public class CupboardController { public ResponseEntity checkoutNeeds(@RequestBody Map data, @RequestHeader("jelly-api-key") String key) { int needID = data.get("needID"); int checkoutAmount = data.get("amount"); - LOG.log(Level.INFO, "PUT /need/checkout body: {0}", data); + LOG.log(Level.INFO, "PUT /need/checkout body={0}", data); try { cupboardService.checkoutNeed(needID, checkoutAmount, key); return new ResponseEntity<>(HttpStatus.OK); diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/UserController.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/UserController.java index cd340ef..d2f3f28 100644 --- a/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/UserController.java +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/UserController.java @@ -45,7 +45,7 @@ public class UserController { */ @PostMapping("") public ResponseEntity createUser(@RequestBody Map params) { - LOG.log(Level.INFO, "POST /users body: {0}", params); + LOG.log(Level.INFO, "POST /users body={0}", params); String username = params.get("username"); String password = params.get("password"); @@ -77,7 +77,7 @@ public class UserController { */ @GetMapping("/{username}") public ResponseEntity getUser(@PathVariable String username, @RequestHeader("jelly-api-key") String key) { - LOG.log(Level.INFO, "GET /user/{0} key: {1}", of(username, key)); + LOG.log(Level.INFO, "GET /user/{0} key={1}", of(username, key)); try { authService.authenticate(username, key); @@ -108,7 +108,7 @@ public class UserController { */ @PutMapping("/{username}") public ResponseEntity updateUser(@RequestBody User user, @PathVariable String username, @RequestHeader("jelly-api-key") String key) { - LOG.log(Level.INFO,"PUT /users/{0} body: {1} key: {2}", of(user, username, key)); + LOG.log(Level.INFO,"PUT /users/{0} body={1} key={2}", of(username, user, key)); try { authService.authenticate(username, key); user = userService.updateUser(user, username); @@ -139,7 +139,7 @@ public class UserController { */ @DeleteMapping("/{username}") public ResponseEntity deleteUser(@PathVariable String username, @RequestHeader("jelly-api-key") String key) { - LOG.log(Level.INFO, "DELETE /users/{0} id: {1}", of(username, key)); + LOG.log(Level.INFO, "DELETE /users/{0} id={1}", of(username, key)); try { authService.authenticate(username, key); -- cgit v1.2.3 From 5f03e80712f7a18370b5118fde5327bde1b6fbbf Mon Sep 17 00:00:00 2001 From: sowgro Date: Tue, 25 Mar 2025 10:17:55 -0400 Subject: Fix tests and more cleanup --- .../api/ufundapi/controller/UserController.java | 3 +-- .../java/com/ufund/api/ufundapi/model/User.java | 4 +-- .../api/ufundapi/persistence/CupboardFileDAO.java | 7 +++--- .../api/ufundapi/persistence/UserFileDAO.java | 11 +++----- .../ufund/api/ufundapi/service/UserService.java | 1 - .../ufundapi/controller/AuthControllerTest.java | 7 +++--- .../ufundapi/controller/UserControllerTest.java | 2 +- .../ufundapi/persistence/CupboardFileDAOTest.java | 5 ++-- .../ufundapi/persistence/UserAuthFileDAOTest.java | 16 ++++++------ .../api/ufundapi/service/AuthServiceTest.java | 29 +++++++++++----------- .../api/ufundapi/service/CupboardServiceTest.java | 27 +++++++++----------- .../api/ufundapi/service/UserServiceTest.java | 13 +++++----- 12 files changed, 56 insertions(+), 69 deletions(-) diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/UserController.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/UserController.java index d2f3f28..c2d9e06 100644 --- a/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/UserController.java +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/UserController.java @@ -1,7 +1,6 @@ package com.ufund.api.ufundapi.controller; import java.io.IOException; -import java.security.InvalidParameterException; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; @@ -117,7 +116,7 @@ public class UserController { } else { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } - } catch (InvalidParameterException ex) { + } catch (IllegalArgumentException ex) { LOG.log(Level.WARNING, ex.getLocalizedMessage()); return new ResponseEntity<>(HttpStatus.BAD_REQUEST); } catch (IllegalAccessException ex) { diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/model/User.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/model/User.java index d04d8b7..58b62df 100644 --- a/ufund-api/src/main/java/com/ufund/api/ufundapi/model/User.java +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/model/User.java @@ -71,8 +71,8 @@ public class User { return basket.toArray(Integer[]::new); } - public boolean removeBasketNeed(Integer needID) { - return basket.remove(needID); + public void removeBasketNeed(Integer needID) { + basket.remove(needID); } /** diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/CupboardFileDAO.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/CupboardFileDAO.java index 3115204..7efda83 100644 --- a/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/CupboardFileDAO.java +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/CupboardFileDAO.java @@ -55,14 +55,12 @@ public class CupboardFileDAO implements CupboardDAO { /** * Saves the needs to json * - * @return True if the save was successful, false otherwise * @throws IOException If there was an IO issue saving the file */ - private boolean save() throws IOException { + private void save() throws IOException { Need[] needArray = needs.values().toArray(Need[]::new); objectMapper.writeValue(new File(filename), needArray); - return true; } @Override @@ -109,7 +107,8 @@ public class CupboardFileDAO implements CupboardDAO { synchronized (needs) { if (needs.containsKey(id)) { needs.remove(id); - return save(); + save(); + return true; } else { return false; } diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserFileDAO.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserFileDAO.java index 63d864a..0d9b9e4 100644 --- a/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserFileDAO.java +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserFileDAO.java @@ -45,12 +45,10 @@ public class UserFileDAO implements UserDAO { /** * Saves the needs to json * - * @return True if the save was successful, false otherwise * @throws IOException If there was an IO issue saving the file */ - private boolean save() throws IOException { + private void save() throws IOException { objectMapper.writeValue(new File(filename), users.values()); - return true; } @Override @@ -83,9 +81,7 @@ public class UserFileDAO implements UserDAO { public User updateUser(User user) throws IOException { synchronized (users) { if (users.containsKey(user.getUsername())) { - // var old = users.put(user.getUsername(), user); - // user.copyPassword(old); - if (user.getBasket() == null || user.getType() == null) { + if (user.getBasket() == null || user.getType() == null) { // TODO clean this up User oldData = users.get(user.getUsername()); User crutch = new User(oldData.getUsername(), 0, new ArrayList<>(), oldData.getType()); crutch.copyPassword(oldData); @@ -107,7 +103,8 @@ public class UserFileDAO implements UserDAO { synchronized (users) { if (users.containsKey(username)) { users.remove(username); - return save(); + save(); + return true; } else { return false; } diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/service/UserService.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/service/UserService.java index 51283fc..6e27f50 100644 --- a/ufund-api/src/main/java/com/ufund/api/ufundapi/service/UserService.java +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/service/UserService.java @@ -2,7 +2,6 @@ package com.ufund.api.ufundapi.service; import java.io.IOException; -import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; import com.ufund.api.ufundapi.DuplicateKeyException; diff --git a/ufund-api/src/test/java/com/ufund/api/ufundapi/controller/AuthControllerTest.java b/ufund-api/src/test/java/com/ufund/api/ufundapi/controller/AuthControllerTest.java index 3d4637d..f4b5980 100644 --- a/ufund-api/src/test/java/com/ufund/api/ufundapi/controller/AuthControllerTest.java +++ b/ufund-api/src/test/java/com/ufund/api/ufundapi/controller/AuthControllerTest.java @@ -8,7 +8,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static org.mockito.ArgumentMatchers.any; -import org.mockito.Mockito; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -26,7 +25,7 @@ public class AuthControllerTest { private Map authMap; @BeforeEach - private void setupAuthController() { + public void setupAuthController() { mockAuthService = mock(AuthService.class); authController = new AuthController(mockAuthService); @@ -76,7 +75,7 @@ public class AuthControllerTest { } @Test - public void testLogout() throws IllegalAccessException, IOException { + public void testLogout() { // Setup String key = "123"; @@ -88,7 +87,7 @@ public class AuthControllerTest { } @Test - public void testLogoutIOException() throws IllegalAccessException, IOException { + public void testLogoutIOException() throws IOException { // Setup String key = "123"; diff --git a/ufund-api/src/test/java/com/ufund/api/ufundapi/controller/UserControllerTest.java b/ufund-api/src/test/java/com/ufund/api/ufundapi/controller/UserControllerTest.java index 5542f49..cc7df40 100644 --- a/ufund-api/src/test/java/com/ufund/api/ufundapi/controller/UserControllerTest.java +++ b/ufund-api/src/test/java/com/ufund/api/ufundapi/controller/UserControllerTest.java @@ -244,7 +244,7 @@ public class UserControllerTest { ResponseEntity response = userController.updateUser(user, username, key); // Analyze - assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode()); + assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode()); } @Test diff --git a/ufund-api/src/test/java/com/ufund/api/ufundapi/persistence/CupboardFileDAOTest.java b/ufund-api/src/test/java/com/ufund/api/ufundapi/persistence/CupboardFileDAOTest.java index 0ebbeca..d83e825 100644 --- a/ufund-api/src/test/java/com/ufund/api/ufundapi/persistence/CupboardFileDAOTest.java +++ b/ufund-api/src/test/java/com/ufund/api/ufundapi/persistence/CupboardFileDAOTest.java @@ -23,11 +23,10 @@ import com.ufund.api.ufundapi.model.Need.GoalType; public class CupboardFileDAOTest { private CupboardFileDAO cupboardFileDao; private Need[] testNeeds; - private ObjectMapper mockObjectMapper; - @BeforeEach + @BeforeEach public void setupCupboardFileDao() throws IOException { - mockObjectMapper = mock(ObjectMapper.class); + ObjectMapper mockObjectMapper = mock(ObjectMapper.class); testNeeds = new Need[] { new Need("one", 0, 100, Need.GoalType.MONETARY), new Need("two", 1, 100, Need.GoalType.MONETARY), diff --git a/ufund-api/src/test/java/com/ufund/api/ufundapi/persistence/UserAuthFileDAOTest.java b/ufund-api/src/test/java/com/ufund/api/ufundapi/persistence/UserAuthFileDAOTest.java index f7db747..5e92deb 100644 --- a/ufund-api/src/test/java/com/ufund/api/ufundapi/persistence/UserAuthFileDAOTest.java +++ b/ufund-api/src/test/java/com/ufund/api/ufundapi/persistence/UserAuthFileDAOTest.java @@ -2,6 +2,7 @@ package com.ufund.api.ufundapi.persistence; import java.io.File; import java.io.IOException; +import java.time.LocalDateTime; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -18,22 +19,21 @@ import com.ufund.api.ufundapi.model.UserAuth; public class UserAuthFileDAOTest { private UserAuthFIleDAO userAuthFIleDAO; - private ObjectMapper mockObjectMapper; private UserAuth[] userAuths; @BeforeEach public void setupUserAuthFileDAO() throws IOException { - mockObjectMapper = mock(ObjectMapper.class); + ObjectMapper mockObjectMapper = mock(ObjectMapper.class); userAuths = new UserAuth[]{ - new UserAuth("123", "Phil", null), - new UserAuth("456", "Bob", null), - new UserAuth("789", "Steve", null) + new UserAuth("123", "Phil", LocalDateTime.MAX), + new UserAuth("456", "Bob", LocalDateTime.MAX), + new UserAuth("789", "Steve", LocalDateTime.MAX) }; // When the object mapper is supposed to read from the file // the mock object mapper will return the hero array above when(mockObjectMapper - .readValue(new File("doesnt_matter.txt"),UserAuth[].class)) + .readValue(new File("doesnt_matter.txt"),UserAuth[].class)) .thenReturn(userAuths); userAuthFIleDAO = new UserAuthFIleDAO(mockObjectMapper, "doesnt_matter.txt"); } @@ -47,14 +47,14 @@ public class UserAuthFileDAOTest { } @Test - public void addUserAuthTest() throws IOException { + public void addUserAuthTest() { UserAuth auth = new UserAuth("999", "Fish", null); assertDoesNotThrow(() -> userAuthFIleDAO.addUserAuth(auth)); } @Test - public void removeUserAuthTest() throws IOException { + public void removeUserAuthTest() { String key = "123"; assertDoesNotThrow(() -> userAuthFIleDAO.removeUserAuth(key)); diff --git a/ufund-api/src/test/java/com/ufund/api/ufundapi/service/AuthServiceTest.java b/ufund-api/src/test/java/com/ufund/api/ufundapi/service/AuthServiceTest.java index 55cf7a9..d3085e5 100644 --- a/ufund-api/src/test/java/com/ufund/api/ufundapi/service/AuthServiceTest.java +++ b/ufund-api/src/test/java/com/ufund/api/ufundapi/service/AuthServiceTest.java @@ -11,7 +11,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; -import com.ufund.api.ufundapi.DuplicateKeyException; import com.ufund.api.ufundapi.model.User; import com.ufund.api.ufundapi.model.UserAuth; import com.ufund.api.ufundapi.persistence.UserAuthDAO; @@ -51,16 +50,16 @@ public class AuthServiceTest { } -// @Test -// public void testAuthenticateMismatchName() throws IOException { -// // Mock -// when(mockAuthDAO.getUserAuth(key)).thenReturn(new UserAuth(key, "EvilFish", null)); -// when(mockUserService.getUser("EvilFish")).thenReturn(user); -// -// // Analyze -// assertThrows(IllegalAccessException.class, () -> authService.authenticate(username, key)); -// -// } + @Test + public void testAuthenticateMismatchName() throws IOException { + // Mock + when(mockAuthDAO.getUserAuth(key)).thenReturn(new UserAuth(key, "EvilFish", null)); + when(mockUserService.getUser("EvilFish")).thenReturn(user); + + // Analyze + assertThrows(IllegalAccessException.class, () -> authService.authenticate(username, key)); + + } @Test public void testAuthenticateMissingUserAuth() throws IOException { @@ -73,7 +72,7 @@ public class AuthServiceTest { } @Test - public void testLogin() throws IOException, DuplicateKeyException, IllegalAccessException { + public void testLogin() throws IOException { // Mock when(mockUserService.getUser(username)).thenReturn(user); @@ -83,7 +82,7 @@ public class AuthServiceTest { } @Test - public void testLoginNullUser() throws IOException, DuplicateKeyException, IllegalAccessException { + public void testLoginNullUser() throws IOException { // Mock when(mockUserService.getUser(username)).thenReturn(null); @@ -92,7 +91,7 @@ public class AuthServiceTest { } @Test - public void testLoginMismatchPasswords() throws IOException, DuplicateKeyException, IllegalAccessException { + public void testLoginMismatchPasswords() throws IOException { // Mock when(mockUserService.getUser(username)).thenReturn(User.create(username, "fries")); @@ -101,7 +100,7 @@ public class AuthServiceTest { } @Test - public void testLogout() throws IOException, DuplicateKeyException, IllegalAccessException { + public void testLogout() { // Analyze assertDoesNotThrow(() -> authService.logout(key)); diff --git a/ufund-api/src/test/java/com/ufund/api/ufundapi/service/CupboardServiceTest.java b/ufund-api/src/test/java/com/ufund/api/ufundapi/service/CupboardServiceTest.java index 59f5b1b..05ea2e8 100644 --- a/ufund-api/src/test/java/com/ufund/api/ufundapi/service/CupboardServiceTest.java +++ b/ufund-api/src/test/java/com/ufund/api/ufundapi/service/CupboardServiceTest.java @@ -23,12 +23,11 @@ public class CupboardServiceTest { private CupboardDAO mockCupboardDAO; private CupboardService cupboardService; - private AuthService mockAuthService; @BeforeEach public void setupCupboardService() { mockCupboardDAO = mock(CupboardDAO.class); - mockAuthService = mock(AuthService.class); + AuthService mockAuthService = mock(AuthService.class); cupboardService = new CupboardService(mockAuthService, mockCupboardDAO); } @@ -54,7 +53,7 @@ public class CupboardServiceTest { } @Test - public void testCreateNeedBadGoal() throws IOException, DuplicateKeyException { + public void testCreateNeedBadGoal() throws IOException { // Setup String name = "Jellyfish"; double maxGoal = -100.00; @@ -69,13 +68,12 @@ public class CupboardServiceTest { // Need response = cupboardService.createNeed(name, maxGoal, type); // Analyze - assertThrows(IllegalArgumentException.class, () -> { - cupboardService.createNeed(name, maxGoal, type); - }); + assertThrows(IllegalArgumentException.class, () -> + cupboardService.createNeed(name, maxGoal, type)); } @Test - public void testCreateNeedDuplicate() throws IOException, DuplicateKeyException { + public void testCreateNeedDuplicate() throws IOException { // Setup String name = "Jellyfish"; double maxGoal = 100.00; @@ -91,13 +89,12 @@ public class CupboardServiceTest { // Need response = cupboardService.createNeed(name, maxGoal, type); // Analyze - assertThrows(DuplicateKeyException.class, () -> { - cupboardService.createNeed(name, maxGoal, type); - }); + assertThrows(DuplicateKeyException.class, () -> + cupboardService.createNeed(name, maxGoal, type)); } @Test - public void testSearchNeeds() throws IOException, DuplicateKeyException { + public void testSearchNeeds() throws IOException { // Setup String name = "Jellyfish"; double maxGoal = 100.00; @@ -117,7 +114,7 @@ public class CupboardServiceTest { } @Test - public void testSearchNeedsFail() throws IOException, DuplicateKeyException { + public void testSearchNeedsFail() throws IOException { // Setup String name = "Jellyfish"; double maxGoal = 100.00; @@ -136,7 +133,7 @@ public class CupboardServiceTest { } @Test - public void testGetNeed() throws IOException, DuplicateKeyException { + public void testGetNeed() throws IOException { // Setup String name = "Jellyfish"; double maxGoal = 100.00; @@ -155,7 +152,7 @@ public class CupboardServiceTest { } @Test - public void testUpdateNeed() throws IOException, DuplicateKeyException { + public void testUpdateNeed() throws IOException { // Setup String name = "Jellyfish"; double maxGoal = 100.00; @@ -175,7 +172,7 @@ public class CupboardServiceTest { } @Test - public void testDeleteNeed() throws IOException, DuplicateKeyException { + public void testDeleteNeed() throws IOException { // Setup String name = "Jellyfish"; double maxGoal = 100.00; diff --git a/ufund-api/src/test/java/com/ufund/api/ufundapi/service/UserServiceTest.java b/ufund-api/src/test/java/com/ufund/api/ufundapi/service/UserServiceTest.java index e57c5a3..5adabf1 100644 --- a/ufund-api/src/test/java/com/ufund/api/ufundapi/service/UserServiceTest.java +++ b/ufund-api/src/test/java/com/ufund/api/ufundapi/service/UserServiceTest.java @@ -19,13 +19,12 @@ public class UserServiceTest { private UserService userService; private UserDAO mockUserDAO; - private CupboardService mockCupboardService; @BeforeEach public void setupUserService() { mockUserDAO = mock(UserDAO.class); - mockCupboardService = mock(CupboardService.class); + CupboardService mockCupboardService = mock(CupboardService.class); userService = new UserService(mockUserDAO, mockCupboardService); } @@ -47,7 +46,7 @@ public class UserServiceTest { } @Test - public void testCreateUserDuplicate() throws IOException, DuplicateKeyException { + public void testCreateUserDuplicate() throws IOException { // Setup String username = "Jelly"; String password = "Fish"; @@ -62,7 +61,7 @@ public class UserServiceTest { } @Test - public void testGetUser() throws IOException, DuplicateKeyException { + public void testGetUser() throws IOException { // Setup String username = "Jelly"; String password = "Fish"; @@ -76,7 +75,7 @@ public class UserServiceTest { } @Test - public void testUpdateUser() throws IOException, DuplicateKeyException { + public void testUpdateUser() throws IOException { // Setup String username = "Jelly"; String password = "Fish"; @@ -94,7 +93,7 @@ public class UserServiceTest { } @Test - public void testUpdateUserDifferentUsernames() throws IOException, DuplicateKeyException { + public void testUpdateUserDifferentUsernames() throws IOException { // Setup String username = "Jelly"; String password = "Fish"; @@ -112,7 +111,7 @@ public class UserServiceTest { } @Test - public void testDeleteUser() throws IOException, DuplicateKeyException { + public void testDeleteUser() throws IOException { // Setup String username = "Jelly"; String password = "Fish"; -- cgit v1.2.3 From b0369f8b5e50eaec22c9178748f57dde6912d383 Mon Sep 17 00:00:00 2001 From: Gunther6070 Date: Tue, 25 Mar 2025 18:07:45 -0400 Subject: Created signup component and implemented some functionality. Did not finish implementing color bar and error messages. --- .../ufundapi/controller/CupboardController.java | 2 +- ufund-ui/src/app/app-routing.module.ts | 4 +- ufund-ui/src/app/app.module.ts | 2 + .../src/app/components/signup/signup.component.css | 16 +++ .../app/components/signup/signup.component.html | 7 ++ .../src/app/components/signup/signup.component.ts | 118 +++++++++++++++++++++ 6 files changed, 147 insertions(+), 2 deletions(-) create mode 100644 ufund-ui/src/app/components/signup/signup.component.css create mode 100644 ufund-ui/src/app/components/signup/signup.component.html create mode 100644 ufund-ui/src/app/components/signup/signup.component.ts diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/CupboardController.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/CupboardController.java index e62d5ab..d2029ed 100644 --- a/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/CupboardController.java +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/CupboardController.java @@ -173,7 +173,7 @@ public class CupboardController { /** * Checks out a need by checkoutAmount * - * @param data JSON object with paramters needID and amount + * @param data JSON object with parameters needID and amount * @param key Key used to authenticate user * @return OK if successful, other statuses if failure */ diff --git a/ufund-ui/src/app/app-routing.module.ts b/ufund-ui/src/app/app-routing.module.ts index 4b76654..a6ea806 100644 --- a/ufund-ui/src/app/app-routing.module.ts +++ b/ufund-ui/src/app/app-routing.module.ts @@ -6,6 +6,7 @@ import {LoginComponent} from './components/login/login.component'; import {HomePageComponent} from './components/home-page/home-page.component'; import {FundingBasketComponent} from './components/funding-basket/funding-basket.component'; import {NeedPageComponent} from './components/need-page/need-page.component'; +import {SignupComponent} from './components/signup/signup.component'; const routes: Routes = [ {path: '', component: HomePageComponent}, @@ -13,7 +14,8 @@ const routes: Routes = [ {path: 'cupboard', component: CupboardComponent}, {path: 'dashboard', component: DashboardComponent}, {path: 'basket', component: FundingBasketComponent}, - {path: 'need/:id', component: NeedPageComponent} + {path: 'need/:id', component: NeedPageComponent}, + {path: 'signup', component: SignupComponent}, ]; @NgModule({ diff --git a/ufund-ui/src/app/app.module.ts b/ufund-ui/src/app/app.module.ts index 9f525fe..156ef5f 100644 --- a/ufund-ui/src/app/app.module.ts +++ b/ufund-ui/src/app/app.module.ts @@ -14,6 +14,7 @@ import {RouterLink, RouterLinkActive, RouterOutlet} from '@angular/router'; import {DashboardComponent} from './components/dashboard/dashboard.component'; import {CommonModule} from '@angular/common'; import {LoginComponent} from './components/login/login.component'; +import { SignupComponent } from './components/signup/signup.component'; @NgModule({ declarations: [ @@ -25,6 +26,7 @@ import {LoginComponent} from './components/login/login.component'; NeedListComponent, DashboardComponent, LoginComponent, + SignupComponent, ], imports: [ BrowserModule, diff --git a/ufund-ui/src/app/components/signup/signup.component.css b/ufund-ui/src/app/components/signup/signup.component.css new file mode 100644 index 0000000..2a10016 --- /dev/null +++ b/ufund-ui/src/app/components/signup/signup.component.css @@ -0,0 +1,16 @@ +:host, .border { + display: flex; + flex-direction: column; + max-width: 300px; + gap: 5px +} + +.border { + border-style: solid; + border-width: 1px; + padding: 10px; + margin: 10px; + position: absolute; + background-color: white; + box-shadow: 0 0 10px 10px black; +} diff --git a/ufund-ui/src/app/components/signup/signup.component.html b/ufund-ui/src/app/components/signup/signup.component.html new file mode 100644 index 0000000..742b8cf --- /dev/null +++ b/ufund-ui/src/app/components/signup/signup.component.html @@ -0,0 +1,7 @@ +

Signup:

+ + + +{{statusText | async}} +{{strength | async}} +Account created Proceed to login diff --git a/ufund-ui/src/app/components/signup/signup.component.ts b/ufund-ui/src/app/components/signup/signup.component.ts new file mode 100644 index 0000000..48c6387 --- /dev/null +++ b/ufund-ui/src/app/components/signup/signup.component.ts @@ -0,0 +1,118 @@ +import { Component } from '@angular/core'; +import {UsersService} from '../../services/users.service'; +import {Router} from '@angular/router'; +import {BehaviorSubject} from 'rxjs'; + +@Component({ + selector: 'app-signup', + standalone: false, + templateUrl: './signup.component.html', + styleUrl: './signup.component.css' +}) +export class SignupComponent { + + protected statusText = new BehaviorSubject("") + protected showSuccessMessage = new BehaviorSubject(false) + protected passwordStrength = new BehaviorSubject("") + protected strength = new BehaviorSubject(0) + + constructor( + protected usersService: UsersService, + protected router: Router, + ) {} + + signup(username: string | null, password: string | null) { + console.log(`attempting to sign up with ${username} ${password}`) + if (!username || !password) { + return; + } + + this.usersService.createUser(username, password).then(() => { + this.showSuccessMessage.next(true); + }).catch(ex => { + this.statusText.next("Unable to create account: " + friendlyHttpStatus[ex.status]) + console.log(ex) + }) + } + + checkPasswordStrength(password: string) { + this.statusText.next("") + if (password.match(/[^!-~]/g)) { + this.statusText.next("Invalid characters") + return + } + + let strength = 0; + if (password.length > 6) { + strength++ + console.log("Long") + } + if (password.length > 12) { + strength++ + console.log("Longer") + } + if (password.match(/[a-z]/g)) { + strength++ + console.log("a") + } + if (password.match(/[0-9]/g)) { + strength++ + console.log("0") + } + if (password.match(/[A-Z]/g)) { + strength++ + console.log("A") + } + if (password.match(/[!-/]/g)) { + strength++ + console.log("!") + } + + this.strength.next(strength) + } + +} + +let friendlyHttpStatus: {[key: number]: string} = { + 200: 'OK', + 201: 'Created', + 202: 'Accepted', + 203: 'Non-Authoritative Information', + 204: 'No Content', + 205: 'Reset Content', + 206: 'Partial Content', + 300: 'Multiple Choices', + 301: 'Moved Permanently', + 302: 'Found', + 303: 'See Other', + 304: 'Not Modified', + 305: 'Use Proxy', + 306: 'Unused', + 307: 'Temporary Redirect', + 400: 'Bad Request', + 401: 'Unauthorized', + 402: 'Payment Required', + 403: 'Forbidden', + 404: 'Not Found', + 405: 'Method Not Allowed', + 406: 'Not Acceptable', + 407: 'Proxy Authentication Required', + 408: 'Request Timeout', + 409: 'Conflict', + 410: 'Gone', + 411: 'Length Required', + 412: 'Precondition Required', + 413: 'Request Entry Too Large', + 414: 'Request-URI Too Long', + 415: 'Unsupported Media Type', + 416: 'Requested Range Not Satisfiable', + 417: 'Expectation Failed', + 418: 'I\'m a teapot', + 429: 'Too Many Requests', + 500: 'Internal Server Error', + 501: 'Not Implemented', + 502: 'Bad Gateway', + 503: 'Service Unavailable', + 504: 'Gateway Timeout', + 505: 'HTTP Version Not Supported', +}; -- cgit v1.2.3 From ea13cf6ab3b71ff5e83fca876ec71fec1f7b00ae Mon Sep 17 00:00:00 2001 From: sowgro Date: Wed, 26 Mar 2025 15:38:46 -0400 Subject: Make frontend work with the new backend checkout system --- ufund-ui/src/app/app.component.ts | 8 +- .../app/components/cupboard/cupboard.component.ts | 58 +----- .../components/dashboard/dashboard.component.ts | 20 +- .../funding-basket/funding-basket.component.ts | 122 ++++++------ .../src/app/components/login/login.component.ts | 14 +- .../components/need-list/need-list.component.ts | 212 +++++++++++---------- ufund-ui/src/app/services/auth.service.ts | 57 ++++++ ufund-ui/src/app/services/cupboard.service.ts | 30 ++- ufund-ui/src/app/services/users.service.ts | 52 +---- 9 files changed, 286 insertions(+), 287 deletions(-) create mode 100644 ufund-ui/src/app/services/auth.service.ts diff --git a/ufund-ui/src/app/app.component.ts b/ufund-ui/src/app/app.component.ts index 7dc8ffb..86717c4 100644 --- a/ufund-ui/src/app/app.component.ts +++ b/ufund-ui/src/app/app.component.ts @@ -1,7 +1,7 @@ import {Component, OnInit, Inject} from '@angular/core'; -import {UsersService} from './services/users.service'; import {BehaviorSubject} from 'rxjs'; import { DOCUMENT } from '@angular/common'; +import {AuthService} from './services/auth.service'; @Component({ selector: 'app-root', @@ -14,16 +14,16 @@ export class AppComponent implements OnInit { currentUser$: BehaviorSubject = new BehaviorSubject("Logged out."); constructor( - private userService: UsersService, + private authService: AuthService, @Inject(DOCUMENT) private document: Document ) {} reloadPage() { this.document.defaultView?.location.reload(); - } + } ngOnInit() { - this.userService.getCurrentUserSubject().subscribe(r => { + this.authService.getCurrentUserSubject().subscribe(r => { this.currentUser$?.next(r ? "Logged in as " + r.username : "Logged out." diff --git a/ufund-ui/src/app/components/cupboard/cupboard.component.ts b/ufund-ui/src/app/components/cupboard/cupboard.component.ts index 24b3e2d..a812baf 100644 --- a/ufund-ui/src/app/components/cupboard/cupboard.component.ts +++ b/ufund-ui/src/app/components/cupboard/cupboard.component.ts @@ -1,10 +1,10 @@ import { Component, OnInit, ViewChild } from '@angular/core'; import { CupboardService } from '../../services/cupboard.service'; -import { UsersService } from '../../services/users.service'; import { Need, GoalType } from '../../models/Need'; import { userType } from '../../models/User'; import { BehaviorSubject, catchError, of } from 'rxjs'; import { NeedListComponent } from '../need-list/need-list.component'; +import {AuthService} from '../../services/auth.service'; @Component({ selector: 'app-cupboard', @@ -20,7 +20,10 @@ export class CupboardComponent implements OnInit { needs: any; @ViewChild("needList") needList?: NeedListComponent - constructor(private cupboardService: CupboardService, private usersService: UsersService) { } + constructor( + private cupboardService: CupboardService, + private authService: AuthService + ) {} ngOnInit(): void { this.cupboardService.getNeeds().subscribe(n => this.needs = n); @@ -88,7 +91,7 @@ export class CupboardComponent implements OnInit { } isManager() { - const type = this.usersService.getCurrentUser()?.type; + const type = this.authService.getCurrentUser()?.type; return type === ("MANAGER" as unknown as userType); } @@ -105,7 +108,7 @@ export class CupboardComponent implements OnInit { console.log("need:", need); console.log(need.id, need, "need updated"); this.cupboardService.updateNeed(need.id, need) - .pipe(catchError((ex, r) => { + .pipe(catchError((ex, _) => { if (ex.status == 500) { this.statusText.next("Fields cannot be blank"); } else if (ex.status == 400) { @@ -140,7 +143,7 @@ export class CupboardComponent implements OnInit { console.log("need:", need); console.log("form submitted. creating need: ", need); this.cupboardService.createNeed(need) - .pipe(catchError((ex, r) => { + .pipe(catchError((ex, _) => { if (ex.status == 500) { this.statusText.next("Fields cannot be blank"); } else if (ex.status == 400) { @@ -167,48 +170,3 @@ export class CupboardComponent implements OnInit { } } - -let friendlyHttpStatus: { [key: number]: string } = { - 200: 'OK', - 201: 'Created', - 202: 'Accepted', - 203: 'Non-Authoritative Information', - 204: 'No Content', - 205: 'Reset Content', - 206: 'Partial Content', - 300: 'Multiple Choices', - 301: 'Moved Permanently', - 302: 'Found', - 303: 'See Other', - 304: 'Not Modified', - 305: 'Use Proxy', - 306: 'Unused', - 307: 'Temporary Redirect', - 400: 'Bad Request', - 401: 'Unauthorized', - 402: 'Payment Required', - 403: 'Forbidden', - 404: 'Not Found', - 405: 'Method Not Allowed', - 406: 'Not Acceptable', - 407: 'Proxy Authentication Required', - 408: 'Request Timeout', - 409: 'Conflict', - 410: 'Gone', - 411: 'Length Required', - 412: 'Precondition Required', - 413: 'Request Entry Too Large', - 414: 'Request-URI Too Long', - 415: 'Unsupported Media Type', - 416: 'Requested Range Not Satisfiable', - 417: 'Expectation Failed', - 418: 'I\'m a teapot', - 422: 'Unprocessable Entity', - 429: 'Too Many Requests', - 500: 'Internal Server Error', - 501: 'Not Implemented', - 502: 'Bad Gateway', - 503: 'Service Unavailable', - 504: 'Gateway Timeout', - 505: 'HTTP Version Not Supported', -}; diff --git a/ufund-ui/src/app/components/dashboard/dashboard.component.ts b/ufund-ui/src/app/components/dashboard/dashboard.component.ts index b9faefa..a0ad566 100644 --- a/ufund-ui/src/app/components/dashboard/dashboard.component.ts +++ b/ufund-ui/src/app/components/dashboard/dashboard.component.ts @@ -1,21 +1,21 @@ -import { Component } from '@angular/core'; -import { UsersService } from '../../services/users.service'; -import { userType } from '../../models/User'; +import {Component} from '@angular/core'; +import {userType} from '../../models/User'; +import {AuthService} from '../../services/auth.service'; @Component({ - selector: 'app-dashboard', - standalone: false, - templateUrl: './dashboard.component.html', - styleUrl: './dashboard.component.css' + selector: 'app-dashboard', + standalone: false, + templateUrl: './dashboard.component.html', + styleUrl: './dashboard.component.css' }) export class DashboardComponent { constructor( - protected usersService: UsersService, + protected authService: AuthService, ) {} isManager() { - const type = this.usersService.getCurrentUser()?.type; + const type = this.authService.getCurrentUser()?.type; return type === ("MANAGER" as unknown as userType); - } + } } diff --git a/ufund-ui/src/app/components/funding-basket/funding-basket.component.ts b/ufund-ui/src/app/components/funding-basket/funding-basket.component.ts index e654711..faa7e0b 100644 --- a/ufund-ui/src/app/components/funding-basket/funding-basket.component.ts +++ b/ufund-ui/src/app/components/funding-basket/funding-basket.component.ts @@ -1,11 +1,9 @@ import {Component, Input, OnInit, ViewChild} from '@angular/core'; -import {User} from '../../models/User'; -import { UsersService } from '../../services/users.service'; -import { Need } from '../../models/Need'; -import { NeedListComponent } from '../need-list/need-list.component'; -import { Router } from '@angular/router'; -import { CupboardService } from '../../services/cupboard.service'; -import { BehaviorSubject, catchError, firstValueFrom, Observable } from 'rxjs'; +import {UsersService} from '../../services/users.service'; +import {Router} from '@angular/router'; +import {CupboardService} from '../../services/cupboard.service'; +import {catchError, firstValueFrom, Observable} from 'rxjs'; +import {AuthService} from '../../services/auth.service'; @Component({ selector: 'app-funding-basket', @@ -14,67 +12,67 @@ import { BehaviorSubject, catchError, firstValueFrom, Observable } from 'rxjs'; styleUrl: './funding-basket.component.css' }) export class FundingBasketComponent implements OnInit { - statusText: any; + statusText: any; - constructor( - private router: Router, - protected cupboardService: CupboardService, - protected usersService: UsersService - ) {} + constructor( + private router: Router, + protected cupboardService: CupboardService, + protected usersService: UsersService, + private authService: AuthService + ) {} - @ViewChild("contribution") contribution?: Input; - @Input() isValid: boolean = true; + @ViewChild("contribution") contribution?: Input; + @Input() isValid: boolean = true; - // this is for login rerouting - ngOnInit(): void { - if (!this.usersService.getCurrentUser()) { - this.router.navigate(['/login'], {queryParams: {redir: this.router.url}}); - return; - } - - this.usersService.refreshBasket(); - // this.usersService.removeNeed(); <- call this to remove - } + // this is for login rerouting + ngOnInit(): void { + if (!this.authService.getCurrentUser()) { + this.router.navigate(['/login'], {queryParams: {redir: this.router.url}}); + return; + } - async checkout() { - this.isValid = true; - for (let c of document.getElementById("funding-basket")?.querySelectorAll('.contribution')!) { - let contribution = c as HTMLInputElement; - contribution.setAttribute("style",""); - if ( contribution.value == '' || contribution.valueAsNumber <= 0) { - this.isValid = false; - contribution.setAttribute("style","color: #ff0000"); - } + this.usersService.refreshBasket(); + // this.usersService.removeNeed(); <- call this to remove } - if (this.isValid) { - for (let c of document.getElementById("funding-basket")?.querySelectorAll('.contribution')!) { - let contribution = c as HTMLInputElement; - let need = await firstValueFrom(this.cupboardService.getNeed(+contribution.id)); - need.current +=+ contribution.value; - this.usersService.removeNeed(+need.id); - this.cupboardService.updateNeed(need.id, need) - .pipe(catchError((ex, r) => { - if (ex.status == 500) { - this.statusText.next('Fields cannot be blank'); - } else if (ex.status == 400) { - this.statusText.next('Goal must be greater than 0'); - } else { - this.statusText.next('Error on creating need'); - } - return new Observable(); - })) - .subscribe((result) => { - if (result) { - console.log('need updated successfully'); - //this.needList?.refresh() - } else { - console.log('need update failed'); - } - }); - } - } - } + async checkout() { + this.isValid = true; + for (let c of document.getElementById("funding-basket")?.querySelectorAll('.contribution')!) { + let contribution = c as HTMLInputElement; + contribution.setAttribute("style", ""); + if (contribution.value == '' || contribution.valueAsNumber <= 0) { + this.isValid = false; + contribution.setAttribute("style", "color: #ff0000"); + } + } + if (this.isValid) { + for (let c of document.getElementById("funding-basket")?.querySelectorAll('.contribution')!) { + let contribution = c as HTMLInputElement; + let need = await firstValueFrom(this.cupboardService.getNeed(+contribution.id)); + need.current += +contribution.value; + this.usersService.removeNeed(+need.id); + this.cupboardService.checkoutNeed(need.id, +contribution.value) + .pipe(catchError((ex, _) => { + if (ex.status == 500) { + this.statusText.next('Fields cannot be blank'); + } else if (ex.status == 400) { + this.statusText.next('Goal must be greater than 0'); + } else { + this.statusText.next('Error on creating need'); + } + return new Observable(); + })) + .subscribe((result) => { + if (result) { + console.log('need updated successfully'); + //this.needList?.refresh() + } else { + console.log('need update failed'); + } + }); + } + } + } } diff --git a/ufund-ui/src/app/components/login/login.component.ts b/ufund-ui/src/app/components/login/login.component.ts index 9d806f5..f6a2996 100644 --- a/ufund-ui/src/app/components/login/login.component.ts +++ b/ufund-ui/src/app/components/login/login.component.ts @@ -2,12 +2,13 @@ import {Component, OnInit} from '@angular/core' import {UsersService} from '../../services/users.service'; import {ActivatedRoute, Router} from '@angular/router'; import {BehaviorSubject} from 'rxjs'; +import {AuthService} from '../../services/auth.service'; @Component({ - selector: 'app-login', - standalone: false, - templateUrl: './login.component.html', - styleUrl: './login.component.css' + selector: 'app-login', + standalone: false, + templateUrl: './login.component.html', + styleUrl: './login.component.css' }) export class LoginComponent implements OnInit { @@ -17,7 +18,8 @@ export class LoginComponent implements OnInit { constructor( protected usersService: UsersService, protected router: Router, - private route: ActivatedRoute + private route: ActivatedRoute, + private authService: AuthService ) {} ngOnInit() { @@ -31,7 +33,7 @@ export class LoginComponent implements OnInit { return; } - this.usersService.login(username, password).then(() => { + this.authService.login(username, password).then(() => { this.router.navigate([next]); }).catch(ex => { this.statusText.next("Unable to login: " + friendlyHttpStatus[ex.status]) diff --git a/ufund-ui/src/app/components/need-list/need-list.component.ts b/ufund-ui/src/app/components/need-list/need-list.component.ts index 25f05d6..3a89a20 100644 --- a/ufund-ui/src/app/components/need-list/need-list.component.ts +++ b/ufund-ui/src/app/components/need-list/need-list.component.ts @@ -1,132 +1,138 @@ -import { Component } from '@angular/core'; +import {Component} from '@angular/core'; import {Need} from '../../models/Need'; import {CupboardService} from '../../services/cupboard.service'; -import { UsersService } from '../../services/users.service'; -import { userType } from '../../models/User'; +import {UsersService} from '../../services/users.service'; +import {userType} from '../../models/User'; +import {catchError, of} from 'rxjs'; +import {AuthService} from '../../services/auth.service'; + @Component({ - selector: 'app-need-list', - standalone: false, - templateUrl: './need-list.component.html', - styleUrl: './need-list.component.css' + selector: 'app-need-list', + standalone: false, + templateUrl: './need-list.component.html', + styleUrl: './need-list.component.css' }) export class NeedListComponent { - needs: Need[] = []; - searchResults: Need[] = []; + needs: Need[] = []; + searchResults: Need[] = []; - constructor( - private cupboardService: CupboardService, - private usersService: UsersService - ) {} + constructor( + private cupboardService: CupboardService, + private usersService: UsersService, + private authService: AuthService + ) {} refresh() { this.cupboardService.getNeeds().subscribe(n => this.needs = n) } - ngOnInit(): void { - this.refresh() - - this.close(); - } + ngOnInit(): void { + this.refresh() - private showElement(element: any) { - if (element){ - element.style.visibility = 'visible'; - element.style.position = 'relative'; + this.close(); } - } - private hideElement(element: any) { - if (element){ - element.style.visibility = 'hidden'; - element.style.position = 'absolute'; + private showElement(element: any) { + if (element) { + element.style.visibility = 'visible'; + element.style.position = 'relative'; + } } - } - private updateSearchStatus(text: string) { - let element = document.getElementById('search-status'); - if (element) { - element.innerHTML = text; + private hideElement(element: any) { + if (element) { + element.style.visibility = 'hidden'; + element.style.position = 'absolute'; + } } - } - open() { - this.hideElement(document.getElementById('search-button')); - this.showElement(document.getElementById('search-form')); - } + private updateSearchStatus(text: string) { + let element = document.getElementById('search-status'); + if (element) { + element.innerHTML = text; + } + } - close() { - this.hideElement(document.getElementById('search-form')); - this.showElement(document.getElementById('search-button')); - this.hideElement(document.getElementById('search-status')); - } + open() { + this.hideElement(document.getElementById('search-button')); + this.showElement(document.getElementById('search-form')); + } - private searchDelay: any; + close() { + this.hideElement(document.getElementById('search-form')); + this.showElement(document.getElementById('search-button')); + this.hideElement(document.getElementById('search-status')); + } - async search(form: any) { - //wait .25 seconds before searching but cancel if another search is made during the wait to prevent too many api calls + private searchDelay: any; - //remove previous search if it exists - if (this.searchDelay) { - clearTimeout(this.searchDelay); - } + async search(form: any) { + //wait .25 seconds before searching but cancel if another search is made during the wait to prevent too many api calls - this.searchDelay = setTimeout(() => { - const currentSearchValue = form.search; //latest value of the search - this.cupboardService.searchNeeds(currentSearchValue).subscribe((n) => { - this.searchResults = n; - console.log(currentSearchValue, this.searchResults); - this.showElement(document.getElementById('search-results')); - this.showElement(document.getElementById('search-status')); - if (this.searchResults.length === this.needs.length) { - this.updateSearchStatus("Please refine your search"); - this.searchResults = []; - } else if (this.searchResults.length === 0) { - this.updateSearchStatus("No results found"); - } else { - this.updateSearchStatus("Search results:"); + //remove previous search if it exists + if (this.searchDelay) { + clearTimeout(this.searchDelay); } - }); - }, 250); - } - - delete(id : number) { - this.cupboardService.deleteNeed(id).subscribe(() => { - this.needs = this.needs.filter(n => n.id !== id) - }) - } - - isManager() { - const type = this.usersService.getCurrentUser()?.type; - return type === ("MANAGER" as unknown as userType); - } - - isHelper() { - const type = this.usersService.getCurrentUser()?.type; - return type === ("HELPER" as unknown as userType); - } - - add(need: Need) { - const currentUser = this.usersService.getCurrentUser(); - //console.log("get current user in angular:", currentUser) - if (currentUser) { - if (!currentUser.basket.includes(need.id)) { - currentUser.basket.push(need.id); - this.usersService.updateUser(currentUser).subscribe(() => { - this.usersService.refreshBasket(); - error: (err: any) => { - console.error(err); - } - }); - } else { - window.alert("This need is already in your basket!") - } - + this.searchDelay = setTimeout(() => { + const currentSearchValue = form.search; //latest value of the search + this.cupboardService.searchNeeds(currentSearchValue).subscribe((n) => { + this.searchResults = n; + console.log(currentSearchValue, this.searchResults); + this.showElement(document.getElementById('search-results')); + this.showElement(document.getElementById('search-status')); + if (this.searchResults.length === this.needs.length) { + this.updateSearchStatus("Please refine your search"); + this.searchResults = []; + } else if (this.searchResults.length === 0) { + this.updateSearchStatus("No results found"); + } else { + this.updateSearchStatus("Search results:"); + } + }); + }, 250); + } + + delete(id: number) { + this.cupboardService.deleteNeed(id).subscribe(() => { + this.needs = this.needs.filter(n => n.id !== id) + }) } - } + isManager() { + const type = this.authService.getCurrentUser()?.type; + return type === ("MANAGER" as unknown as userType); + } + + isHelper() { + const type = this.authService.getCurrentUser()?.type; + return type === ("HELPER" as unknown as userType); + } + + add(need: Need) { + const currentUser = this.authService.getCurrentUser(); + //console.log("get current user in angular:", currentUser) + if (currentUser) { + if (!currentUser.basket.includes(need.id)) { + currentUser.basket.push(need.id); + this.usersService.updateUser(currentUser) + .pipe(catchError((err: any, _) => { + console.error(err); + return of(); + })) + .subscribe(() => { + this.usersService.refreshBasket(); + }); + } else { + window.alert("This need is already in your basket!") + } + - back() { - this.searchResults = []; - } + } + + } + + back() { + this.searchResults = []; + } } diff --git a/ufund-ui/src/app/services/auth.service.ts b/ufund-ui/src/app/services/auth.service.ts new file mode 100644 index 0000000..6bc7145 --- /dev/null +++ b/ufund-ui/src/app/services/auth.service.ts @@ -0,0 +1,57 @@ +import {Injectable} from '@angular/core'; +import {BehaviorSubject, firstValueFrom} from 'rxjs'; +import {User} from '../models/User'; +import {HttpClient, HttpHeaders} from '@angular/common/http'; + +@Injectable({ + providedIn: 'root' +}) +export class AuthService { + + private authUrl = "http://localhost:8080/auth" + private userUrl = "http://localhost:8080/users" + + private currentUser : BehaviorSubject = new BehaviorSubject(null); + private apiKey: string = ""; + + httpOptions2 = () => ({ + headers: new HttpHeaders({ + 'Content-Type': 'application/json', + "jelly-api-key": this.apiKey + }), + responseType: "text" as "json" // don't ask me how or why this works, bc i have no clue... + // see the relevant angular bug report https://github.com/angular/angular/issues/18586 + }); + + constructor( + private http: HttpClient + ) {} + + async login(username: string, password: string) { + let res = this.http.post(this.authUrl, {username: username, password: password}, this.httpOptions2()); + this.apiKey = await firstValueFrom(res); + console.log("apikey: "+this.apiKey) + let res2 = this.http.get(`${this.userUrl}/${username}`, { + headers: new HttpHeaders({ + 'Content-Type': 'application/json', + "jelly-api-key": this.apiKey + }) + }) + let currentU = await firstValueFrom(res2); + this.currentUser.next(currentU); + // this.currentUser.subscribe(r => console.log("currentUser: "+r.username)) + } + + getCurrentUserSubject() { + return this.currentUser; + } + + getCurrentUser() { + return this.currentUser.getValue() + } + + getApiKey() { + return this.apiKey; + } + +} diff --git a/ufund-ui/src/app/services/cupboard.service.ts b/ufund-ui/src/app/services/cupboard.service.ts index 9e14106..9232c0c 100644 --- a/ufund-ui/src/app/services/cupboard.service.ts +++ b/ufund-ui/src/app/services/cupboard.service.ts @@ -2,6 +2,7 @@ import {Injectable} from '@angular/core'; import {HttpClient, HttpHeaders} from '@angular/common/http'; import {Need} from '../models/Need'; import {Observable} from 'rxjs'; +import {AuthService} from './auth.service'; @Injectable({ providedIn: 'root' @@ -9,35 +10,44 @@ import {Observable} from 'rxjs'; export class CupboardService { private url = "http://localhost:8080/cupboard" - private httpOptions = { - headers: new HttpHeaders({'Content-Type': 'application/json'}) - }; + + httpOptions = () => ({ + headers: new HttpHeaders({ + 'Content-Type': 'application/json', + "jelly-api-key": this.authService.getApiKey() + }) + }); constructor( - private http: HttpClient + private http: HttpClient, + private authService: AuthService ) {} createNeed(need: Need): Observable { - return this.http.post(this.url, need, this.httpOptions) + return this.http.post(this.url, need, this.httpOptions()) } getNeeds(): Observable { - return this.http.get(this.url, this.httpOptions) + return this.http.get(this.url, this.httpOptions()) } searchNeeds(name: String): Observable { - return this.http.get(`${this.url}/?name=${name}`, this.httpOptions) + return this.http.get(`${this.url}/?name=${name}`, this.httpOptions()) } getNeed(id: number): Observable { - return this.http.get(`${this.url}/${id}`, this.httpOptions) + return this.http.get(`${this.url}/${id}`, this.httpOptions()) } updateNeed(id: number, data: Need): Observable { - return this.http.put(`${this.url}/${id}`, data, this.httpOptions) + return this.http.put(`${this.url}/${id}`, data, this.httpOptions()) } deleteNeed(id: number): Observable { - return this.http.delete(`${this.url}/${id}`, this.httpOptions) + return this.http.delete(`${this.url}/${id}`, this.httpOptions()) + } + + checkoutNeed(id: number, quantity: number) { + return this.http.put(`${this.url}/checkout`, {needID: id, amount: quantity}, this.httpOptions()) } } diff --git a/ufund-ui/src/app/services/users.service.ts b/ufund-ui/src/app/services/users.service.ts index 6671440..4080ebf 100644 --- a/ufund-ui/src/app/services/users.service.ts +++ b/ufund-ui/src/app/services/users.service.ts @@ -4,36 +4,27 @@ import {BehaviorSubject, catchError, firstValueFrom, Observable, of} from 'rxjs' import {User} from '../models/User'; import { Need } from '../models/Need'; import { CupboardService } from './cupboard.service'; +import {AuthService} from './auth.service'; @Injectable({ providedIn: 'root' }) export class UsersService { - private currentUser : BehaviorSubject = new BehaviorSubject(null); - private apiKey: string = ""; private basket = new BehaviorSubject([]); - private url = "http://localhost:8080/users" - private authUrl = "http://localhost:8080/auth" - private httpOptions = () => ({ + + httpOptions = () => ({ headers: new HttpHeaders({ 'Content-Type': 'application/json', - "jelly-api-key": this.apiKey + "jelly-api-key": this.authService.getApiKey() }) }); - private httpOptions2 = () => ({ - headers: new HttpHeaders({ - 'Content-Type': 'application/json', - "jelly-api-key": this.apiKey - }), - responseType: "text" as "json" // don't ask me how or why this works, bc i have no clue... - // see the relevant angular bug report https://github.com/angular/angular/issues/18586 - }); constructor( private http: HttpClient, private cupboardService: CupboardService, + private authService: AuthService ) {} async createUser(username:string, password:string) { @@ -46,38 +37,15 @@ export class UsersService { updateUser(user: User): Observable { console.log(`${this.url}/${user.username}`, user, this.httpOptions) - return this.http.put(`${this.url}/${user.username}`, user, this.httpOptions2()) // This line is causing issues as the key is not properly being passed + return this.http.put(`${this.url}/${user.username}`, user, this.httpOptions()) } deleteUser(id: number): Observable { return this.http.delete(`${this.url}/${id}`, this.httpOptions()) } - getCurrentUserSubject() { - return this.currentUser; - } - - getCurrentUser() { - return this.currentUser.getValue() - } - - async login(username: string, password: string) { - let res = this.http.post(this.authUrl, {username: username, password: password}, this.httpOptions2()); - this.apiKey = await firstValueFrom(res); - console.log("apikey: "+this.apiKey) - let res2 = this.http.get(`${this.url}/${username}`, { - headers: new HttpHeaders({ - 'Content-Type': 'application/json', - "jelly-api-key": this.apiKey - }) - }) - let currentU = await firstValueFrom(res2); - this.currentUser.next(currentU); - // this.currentUser.subscribe(r => console.log("currentUser: "+r.username)) - } - refreshBasket() { - let promiseArr = this.getCurrentUser()!.basket.map(async needID => { + let promiseArr = this.authService.getCurrentUser()!.basket.map(async needID => { return await firstValueFrom(this.cupboardService.getNeed(needID)); }) Promise.all(promiseArr).then(r => this.basket.next(r)); @@ -86,10 +54,10 @@ export class UsersService { removeNeed(id: number) { let newArr = this.basket.getValue().filter(v => v.id != id); this.basket.next(newArr); - this.getCurrentUser()!.basket = newArr.map(need => need.id); - this.updateUser(this.getCurrentUser()!) + this.authService.getCurrentUser()!.basket = newArr.map(need => need.id); + this.updateUser(this.authService.getCurrentUser()!) .pipe( - catchError((err: any, ob) => { + catchError((err: any, _) => { console.error(err); return of(); }) -- cgit v1.2.3 From ab35efb06b926e8a3aee5cfc8d1fa908aa4a4707 Mon Sep 17 00:00:00 2001 From: sowgro Date: Wed, 26 Mar 2025 18:14:47 -0400 Subject: Fix cupboard access checking and logging --- .../ufundapi/controller/CupboardController.java | 33 ++++++++++++++----- .../api/ufundapi/controller/UserController.java | 12 ++++--- .../ufund/api/ufundapi/service/AuthService.java | 30 +++++++++++++++-- .../api/ufundapi/service/CupboardService.java | 3 +- .../controller/CupboardControllerTest.java | 29 ++++++++++------- .../ufundapi/controller/UserControllerTest.java | 6 ++-- .../api/ufundapi/service/AuthServiceTest.java | 12 +++---- .../components/home-page/home-page.component.ts | 10 +++--- .../components/need-page/need-page.component.ts | 38 +++++++++++----------- ufund-ui/src/app/models/Need.ts | 16 ++++----- ufund-ui/src/app/models/User.ts | 2 -- 11 files changed, 121 insertions(+), 70 deletions(-) diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/CupboardController.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/CupboardController.java index e62d5ab..55ee457 100644 --- a/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/CupboardController.java +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/CupboardController.java @@ -5,6 +5,7 @@ import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; +import com.ufund.api.ufundapi.service.AuthService; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; @@ -23,21 +24,21 @@ import com.ufund.api.ufundapi.model.Need; import com.ufund.api.ufundapi.model.Need.GoalType; import com.ufund.api.ufundapi.service.CupboardService; -import static java.util.List.of; - @RestController @RequestMapping("cupboard") public class CupboardController { private static final Logger LOG = Logger.getLogger(CupboardController.class.getName()); private final CupboardService cupboardService; + private final AuthService authService; /** * Create a cupboard controller to receive REST signals * * @param cupboardService The Data Access Object */ - public CupboardController(CupboardService cupboardService) { + public CupboardController(CupboardService cupboardService, AuthService authService) { this.cupboardService = cupboardService; + this.authService = authService; } /** @@ -50,14 +51,15 @@ public class CupboardController { * INTERNAL_SERVER_ERROR otherwise */ @PostMapping("") - public ResponseEntity createNeed(@RequestBody Map params) { + public ResponseEntity createNeed(@RequestBody Map params, @RequestHeader("jelly-api-key") String key) { LOG.log(Level.INFO, "POST /cupboard body={0}", params); String name = (String) params.get("name"); - double maxGoal = (double) params.get("maxGoal"); + double maxGoal = ((Number) params.get("maxGoal")).doubleValue(); Need.GoalType goalType = GoalType.valueOf((String) params.get("type")); try { + authService.keyHasAccessToCupboard(key); Need need = cupboardService.createNeed(name, maxGoal, goalType); return new ResponseEntity<>(need, HttpStatus.OK); } catch (DuplicateKeyException ex) { @@ -66,6 +68,9 @@ public class CupboardController { } catch (IllegalArgumentException ex) { LOG.log(Level.WARNING, ex.getLocalizedMessage()); return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + } catch (IllegalAccessException ex) { + LOG.log(Level.WARNING, ex.getLocalizedMessage()); + return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); } catch (IOException ex) { LOG.log(Level.SEVERE, ex.getLocalizedMessage()); return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); @@ -152,9 +157,10 @@ public class CupboardController { * @return OK response and the need if it was successful, or INTERNAL_SERVER_ERROR if there was an issue */ @PutMapping("/{id}") - public ResponseEntity updateNeed(@RequestBody Need need, @PathVariable int id) { + public ResponseEntity updateNeed(@RequestBody Need need, @PathVariable int id, @RequestHeader("jelly-api-key") String key) { LOG.log(Level.INFO, "PUT /cupboard/{0} body={1}", of(id, need)); try { + authService.keyHasAccessToCupboard(key); Need updatedNeed = cupboardService.updateNeed(need, id); if (updatedNeed != null) { return new ResponseEntity<>(need, HttpStatus.OK); @@ -164,6 +170,9 @@ public class CupboardController { } catch (IllegalArgumentException ex) { LOG.log(Level.WARNING, ex.getLocalizedMessage()); return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + } catch (IllegalAccessException ex) { + LOG.log(Level.WARNING, ex.getLocalizedMessage()); + return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); } catch (IOException ex) { LOG.log(Level.SEVERE, ex.getLocalizedMessage()); return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); @@ -204,19 +213,27 @@ public class CupboardController { * @return OK if the need was deleted, NOT_FOUND if the need was not found, or INTERNAL_SERVER_ERROR if an error occurred */ @DeleteMapping("/{id}") - public ResponseEntity deleteNeed(@PathVariable int id) { + public ResponseEntity deleteNeed(@PathVariable int id, @RequestHeader("jelly-api-key") String key) { LOG.log(Level.INFO, "DELETE /cupboard/{0}", id); try { + authService.keyHasAccessToCupboard(key); Need need = cupboardService.getNeed(id); if (cupboardService.deleteNeed(id)) { return new ResponseEntity<>(need, HttpStatus.OK); } else { return new ResponseEntity<>(HttpStatus.NOT_FOUND); - } + } + } catch (IllegalAccessException ex) { + LOG.log(Level.WARNING, ex.getLocalizedMessage()); + return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); } catch (IOException ex) { LOG.log(Level.SEVERE, ex.getLocalizedMessage()); return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } } + private Object[] of(Object ...params) { + return params; + } + } diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/UserController.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/UserController.java index c2d9e06..33d2e4f 100644 --- a/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/UserController.java +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/UserController.java @@ -22,8 +22,6 @@ import com.ufund.api.ufundapi.model.User; import com.ufund.api.ufundapi.service.AuthService; import com.ufund.api.ufundapi.service.UserService; -import static java.util.List.of; - @RestController @RequestMapping("users") public class UserController { @@ -79,7 +77,7 @@ public class UserController { LOG.log(Level.INFO, "GET /user/{0} key={1}", of(username, key)); try { - authService.authenticate(username, key); + authService.keyHasAccessToUser(username, key); User user = userService.getUser(username); if (user != null) { return new ResponseEntity<>(user.withoutPasswordHash(), HttpStatus.OK); @@ -109,7 +107,7 @@ public class UserController { public ResponseEntity updateUser(@RequestBody User user, @PathVariable String username, @RequestHeader("jelly-api-key") String key) { LOG.log(Level.INFO,"PUT /users/{0} body={1} key={2}", of(username, user, key)); try { - authService.authenticate(username, key); + authService.keyHasAccessToUser(username, key); user = userService.updateUser(user, username); if (user != null) { return new ResponseEntity<>(user, HttpStatus.OK); @@ -141,7 +139,7 @@ public class UserController { LOG.log(Level.INFO, "DELETE /users/{0} id={1}", of(username, key)); try { - authService.authenticate(username, key); + authService.keyHasAccessToUser(username, key); if (userService.deleteUser(username)) { return new ResponseEntity<>(HttpStatus.OK); } else { @@ -156,4 +154,8 @@ public class UserController { } } + private Object[] of(Object ...params) { + return params; + } + } diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/service/AuthService.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/service/AuthService.java index 4e5ebce..cdce80d 100644 --- a/ufund-api/src/main/java/com/ufund/api/ufundapi/service/AuthService.java +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/service/AuthService.java @@ -25,8 +25,9 @@ public class AuthService { * @param targetUsername The targetUsername of the user trying to be accessed. * @param key The api key obtained by the client from logging in. * @throws IllegalAccessException Thrown if access was denied to the user. + * @throws IOException Thrown on a file writing issue */ - public void authenticate(String targetUsername, String key) throws IllegalAccessException, IOException { + public void keyHasAccessToUser(String targetUsername, String key) throws IllegalAccessException, IOException { var userAuth = userAuthDAO.getUserAuth(key); if (userAuth == null) { throw new IllegalAccessException("Invalid authentication key"); @@ -39,11 +40,36 @@ public class AuthService { } } - public void authenticate(String key) throws IOException, IllegalAccessException { + /** + * Check if the provided key is valid + * @param key The api key obtained by the client from logging in. + * @throws IllegalAccessException Thrown if access was denied to the user. + * @throws IOException Thrown on a file writing issue + */ + public void keyIsValid(String key) throws IOException, IllegalAccessException { + var userAuth = userAuthDAO.getUserAuth(key); + if (userAuth == null) { + throw new IllegalAccessException("Invalid authentication key"); + } + } + + /** + * Check if the provided key has access to edit the cupboard + * @param key The api key obtained by the client from logging in. + * @throws IllegalAccessException Thrown if access was denied to the user. + * @throws IOException Thrown on a file writing issue + */ + public void keyHasAccessToCupboard(String key) throws IOException, IllegalAccessException { var userAuth = userAuthDAO.getUserAuth(key); if (userAuth == null) { throw new IllegalAccessException("Invalid authentication key"); } + + var username = userAuth.getUsername(); + var userType = userService.getUser(username).getType(); + if (userType != User.UserType.MANAGER) { + throw new IllegalAccessException("Provided key does not grant access to perform the requested operation"); + } } /** diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/service/CupboardService.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/service/CupboardService.java index 91e3ba5..aaa8cb8 100644 --- a/ufund-api/src/main/java/com/ufund/api/ufundapi/service/CupboardService.java +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/service/CupboardService.java @@ -111,7 +111,7 @@ public class CupboardService { if (checkoutAmount <= 0) { throw new IllegalArgumentException("Amount must be greater than 0"); } - authService.authenticate(key); + authService.keyIsValid(key); Need need = cupboardDAO.getNeed(id); need.incrementCurrent(checkoutAmount); } @@ -124,6 +124,7 @@ public class CupboardService { * @throws IOException Thrown on any problem removing the need */ public boolean deleteNeed(int id) throws IOException { + return cupboardDAO.deleteNeed(id); } } diff --git a/ufund-api/src/test/java/com/ufund/api/ufundapi/controller/CupboardControllerTest.java b/ufund-api/src/test/java/com/ufund/api/ufundapi/controller/CupboardControllerTest.java index 6ef6710..89697bf 100644 --- a/ufund-api/src/test/java/com/ufund/api/ufundapi/controller/CupboardControllerTest.java +++ b/ufund-api/src/test/java/com/ufund/api/ufundapi/controller/CupboardControllerTest.java @@ -7,10 +7,11 @@ import static java.util.Map.entry; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.Mockito.*; + +import com.ufund.api.ufundapi.service.AuthService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; import org.springframework.http.HttpStatus; import com.ufund.api.ufundapi.DuplicateKeyException; @@ -21,11 +22,17 @@ import com.ufund.api.ufundapi.service.CupboardService; public class CupboardControllerTest { private CupboardController cupboardController; private CupboardService mockCupboardService; + private final String key = "dummyKey"; @BeforeEach public void setupCupboardDAO() { + AuthService mockAuthService = mock(AuthService.class); mockCupboardService = mock(CupboardService.class); - cupboardController = new CupboardController(mockCupboardService); + cupboardController = new CupboardController(mockCupboardService, mockAuthService); + + try { + doThrow().when(mockAuthService).keyHasAccessToCupboard(key); + } catch (Exception ignored) {} } @Test @@ -43,7 +50,7 @@ public class CupboardControllerTest { entry("type", "MONETARY") ); - var res = cupboardController.createNeed(needMap); + var res = cupboardController.createNeed(needMap, key); assertEquals(HttpStatus.OK, res.getStatusCode()); assertEquals(need, res.getBody()); @@ -58,7 +65,7 @@ public class CupboardControllerTest { entry("maxGoal", -100.0), entry("type", "MONETARY")); - var res = cupboardController.createNeed(needMap); + var res = cupboardController.createNeed(needMap, key); assertEquals(HttpStatus.BAD_REQUEST, res.getStatusCode()); } @@ -72,7 +79,7 @@ public class CupboardControllerTest { entry("maxGoal", 100.0), entry("type", "MONETARY")); - var res = cupboardController.createNeed(needMap); + var res = cupboardController.createNeed(needMap, key); assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, res.getStatusCode()); } @@ -174,7 +181,7 @@ public class CupboardControllerTest { var need = new Need("Name", 1, 100, Need.GoalType.MONETARY); when(mockCupboardService.updateNeed(need, 1)).thenReturn(need); - var res = cupboardController.updateNeed(need, 1); + var res = cupboardController.updateNeed(need, 1, key); assertEquals(HttpStatus.OK, res.getStatusCode()); assertEquals(need, res.getBody()); @@ -185,7 +192,7 @@ public class CupboardControllerTest { var need = new Need("Name", 1, 100, Need.GoalType.MONETARY); when(mockCupboardService.updateNeed(need, 1)).thenThrow(new IOException()); - var res = cupboardController.updateNeed(need, 1); + var res = cupboardController.updateNeed(need, 1, key); assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, res.getStatusCode()); } @@ -196,7 +203,7 @@ public class CupboardControllerTest { when(mockCupboardService.getNeed(1)).thenReturn(need); when(mockCupboardService.deleteNeed(1)).thenReturn(true); - var res = cupboardController.deleteNeed(1); + var res = cupboardController.deleteNeed(1, key); assertEquals(HttpStatus.OK, res.getStatusCode()); } @@ -206,7 +213,7 @@ public class CupboardControllerTest { when(mockCupboardService.getNeed(1)).thenReturn(null); when(mockCupboardService.deleteNeed(1)).thenReturn(false); - var res = cupboardController.deleteNeed(1); + var res = cupboardController.deleteNeed(1, key); assertEquals(HttpStatus.NOT_FOUND, res.getStatusCode()); } @@ -217,7 +224,7 @@ public class CupboardControllerTest { when(mockCupboardService.getNeed(1)).thenReturn(need); when(mockCupboardService.deleteNeed(1)).thenThrow(new IOException()); - var res = cupboardController.deleteNeed(1); + var res = cupboardController.deleteNeed(1, key); assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, res.getStatusCode()); } diff --git a/ufund-api/src/test/java/com/ufund/api/ufundapi/controller/UserControllerTest.java b/ufund-api/src/test/java/com/ufund/api/ufundapi/controller/UserControllerTest.java index cc7df40..06fb6cd 100644 --- a/ufund-api/src/test/java/com/ufund/api/ufundapi/controller/UserControllerTest.java +++ b/ufund-api/src/test/java/com/ufund/api/ufundapi/controller/UserControllerTest.java @@ -82,7 +82,7 @@ public class UserControllerTest { String key = UserAuth.generate(username).getKey(); // When getUser is called on the Mock User service, throw an IOException // doThrow(new IllegalAccessException()).when(mockUserService).getUser(username); - doThrow(new IllegalAccessException()).when(mockAuthService).authenticate(username, key); + doThrow(new IllegalAccessException()).when(mockAuthService).keyHasAccessToUser(username, key); // Invoke ResponseEntity response = userController.getUser(username, key); @@ -237,7 +237,7 @@ public class UserControllerTest { String key = UserAuth.generate(username).getKey(); // When updateUser is called on the Mock User service, throw a Invalid Parameter exception // exception - doThrow(new IllegalAccessException()).when(mockAuthService).authenticate(username, key); + doThrow(new IllegalAccessException()).when(mockAuthService).keyHasAccessToUser(username, key); // Invoke @@ -298,7 +298,7 @@ public class UserControllerTest { String username = "Test"; String key = UserAuth.generate(username).getKey(); // When deleteUser is called on the Mock User service, throw an IOException - doThrow(new IllegalAccessException()).when(mockAuthService).authenticate(username, key); + doThrow(new IllegalAccessException()).when(mockAuthService).keyHasAccessToUser(username, key); // Invoke ResponseEntity response = userController.deleteUser(username, key); diff --git a/ufund-api/src/test/java/com/ufund/api/ufundapi/service/AuthServiceTest.java b/ufund-api/src/test/java/com/ufund/api/ufundapi/service/AuthServiceTest.java index d3085e5..4f58b12 100644 --- a/ufund-api/src/test/java/com/ufund/api/ufundapi/service/AuthServiceTest.java +++ b/ufund-api/src/test/java/com/ufund/api/ufundapi/service/AuthServiceTest.java @@ -40,34 +40,34 @@ public class AuthServiceTest { } @Test - public void testAuthenticate() throws IOException { + public void testKeyIsValid() throws IOException { // Mock when(mockAuthDAO.getUserAuth(key)).thenReturn(new UserAuth(key, username, null)); when(mockUserService.getUser(username)).thenReturn(user); // Analyze - assertDoesNotThrow(() -> authService.authenticate(username, key)); + assertDoesNotThrow(() -> authService.keyHasAccessToUser(username, key)); } @Test - public void testAuthenticateMismatchName() throws IOException { + public void testKeyIsValidMismatchName() throws IOException { // Mock when(mockAuthDAO.getUserAuth(key)).thenReturn(new UserAuth(key, "EvilFish", null)); when(mockUserService.getUser("EvilFish")).thenReturn(user); // Analyze - assertThrows(IllegalAccessException.class, () -> authService.authenticate(username, key)); + assertThrows(IllegalAccessException.class, () -> authService.keyHasAccessToUser(username, key)); } @Test - public void testAuthenticateMissingUserAuth() throws IOException { + public void testKeyIsValidMissingUserAuth() throws IOException { // Mock when(mockAuthDAO.getUserAuth(key)).thenReturn(null); // Analyze - assertThrows(IllegalAccessException.class, () -> authService.authenticate(username, key)); + assertThrows(IllegalAccessException.class, () -> authService.keyHasAccessToUser(username, key)); } diff --git a/ufund-ui/src/app/components/home-page/home-page.component.ts b/ufund-ui/src/app/components/home-page/home-page.component.ts index 5b2277c..95e8962 100644 --- a/ufund-ui/src/app/components/home-page/home-page.component.ts +++ b/ufund-ui/src/app/components/home-page/home-page.component.ts @@ -1,10 +1,10 @@ -import { Component } from '@angular/core'; +import {Component} from '@angular/core'; @Component({ - selector: 'app-home-page', - standalone: false, - templateUrl: './home-page.component.html', - styleUrl: './home-page.component.css' + selector: 'app-home-page', + standalone: false, + templateUrl: './home-page.component.html', + styleUrl: './home-page.component.css' }) export class HomePageComponent { diff --git a/ufund-ui/src/app/components/need-page/need-page.component.ts b/ufund-ui/src/app/components/need-page/need-page.component.ts index 597d0e0..e38554c 100644 --- a/ufund-ui/src/app/components/need-page/need-page.component.ts +++ b/ufund-ui/src/app/components/need-page/need-page.component.ts @@ -2,30 +2,30 @@ import {Component, Input} from '@angular/core'; import {GoalType, Need} from '../../models/Need'; import {ActivatedRoute} from "@angular/router"; import {CupboardService} from "../../services/cupboard.service"; -import { NgFor } from '@angular/common'; +import {NgFor} from '@angular/common'; @Component({ - selector: 'app-need-page', - standalone: false, - templateUrl: './need-page.component.html', - styleUrl: './need-page.component.css' + selector: 'app-need-page', + standalone: false, + templateUrl: './need-page.component.html', + styleUrl: './need-page.component.css' }) export class NeedPageComponent { - constructor( - private route: ActivatedRoute, - private cupboardService: CupboardService, - ) {} + constructor( + private route: ActivatedRoute, + private cupboardService: CupboardService, + ) {} - public GoalType = GoalType; + public GoalType = GoalType; - @Input() need?: Need; + @Input() need?: Need; - ngOnInit(): void { - const id = Number(this.route.snapshot.paramMap.get('id')); - this.cupboardService.getNeed(id).subscribe(n => this.need = n); - } + ngOnInit(): void { + const id = Number(this.route.snapshot.paramMap.get('id')); + this.cupboardService.getNeed(id).subscribe(n => this.need = n); + } - back() { - window.history.back(); - } -} \ No newline at end of file + back() { + window.history.back(); + } +} diff --git a/ufund-ui/src/app/models/Need.ts b/ufund-ui/src/app/models/Need.ts index 9e97fd4..5cd4e39 100644 --- a/ufund-ui/src/app/models/Need.ts +++ b/ufund-ui/src/app/models/Need.ts @@ -1,13 +1,13 @@ export interface Need { - name: string, - id: number, - filterAttributes: string[], - type: GoalType; - maxGoal: number; - current: number; + name: string, + id: number, + filterAttributes: string[], + type: GoalType; + maxGoal: number; + current: number; } export enum GoalType { - MONETARY, - PHYSICAL + MONETARY, + PHYSICAL } diff --git a/ufund-ui/src/app/models/User.ts b/ufund-ui/src/app/models/User.ts index f4396f6..e6848fa 100644 --- a/ufund-ui/src/app/models/User.ts +++ b/ufund-ui/src/app/models/User.ts @@ -1,5 +1,3 @@ -import {Need} from './Need'; - export enum userType { HELPER, MANAGER -- cgit v1.2.3 From 5dfb1327c4507ae1613debb5b485fd74edff33db Mon Sep 17 00:00:00 2001 From: sowgro Date: Wed, 26 Mar 2025 19:00:04 -0400 Subject: fix expiration logic and cleanup --- ufund-api/src/main/java/com/ufund/api/ufundapi/model/Need.java | 2 +- .../main/java/com/ufund/api/ufundapi/persistence/UserAuthFIleDAO.java | 2 +- .../src/main/java/com/ufund/api/ufundapi/persistence/UserFileDAO.java | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/model/Need.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/model/Need.java index 786b104..22e86e3 100644 --- a/ufund-api/src/main/java/com/ufund/api/ufundapi/model/Need.java +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/model/Need.java @@ -38,7 +38,7 @@ public class Need { * @param maxGoal The maximum goal for this need * @param type The type of need (monetary, physical) */ - public Need(String name, GoalType type, double maxGoal) { // TODO why is this needed + public Need(String name, GoalType type, double maxGoal) { this.name = name; this.type = type; this.maxGoal = maxGoal; diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserAuthFIleDAO.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserAuthFIleDAO.java index 9023b42..7bda3f9 100644 --- a/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserAuthFIleDAO.java +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserAuthFIleDAO.java @@ -37,7 +37,7 @@ public class UserAuthFIleDAO implements UserAuthDAO { UserAuth[] userAuthKeysArray = objectMapper.readValue(new File(filename), UserAuth[].class); for (UserAuth userAuth : userAuthKeysArray) { - if (userAuth.getExpiration().compareTo(LocalDateTime.now()) > -1) { // Someone else double check the logic is correct. Checks if auth is valid and adds if so + if (userAuth.getExpiration().isBefore(LocalDateTime.now())) { userAuthMap.put(userAuth.getKey(), userAuth); } } diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserFileDAO.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserFileDAO.java index 0d9b9e4..4b09449 100644 --- a/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserFileDAO.java +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserFileDAO.java @@ -81,7 +81,8 @@ public class UserFileDAO implements UserDAO { public User updateUser(User user) throws IOException { synchronized (users) { if (users.containsKey(user.getUsername())) { - if (user.getBasket() == null || user.getType() == null) { // TODO clean this up + if (user.getBasket() == null || user.getType() == null) { + System.err.println("CRUTCH HAPPENED"); User oldData = users.get(user.getUsername()); User crutch = new User(oldData.getUsername(), 0, new ArrayList<>(), oldData.getType()); crutch.copyPassword(oldData); -- cgit v1.2.3 From 959b5bbaaa370542b75d804cedbbbecea881df0f Mon Sep 17 00:00:00 2001 From: Gunther6070 Date: Wed, 26 Mar 2025 19:12:46 -0400 Subject: Added a progress bar to signup --- ufund-ui/src/app/components/signup/signup.component.css | 8 ++++++++ ufund-ui/src/app/components/signup/signup.component.html | 1 + 2 files changed, 9 insertions(+) diff --git a/ufund-ui/src/app/components/signup/signup.component.css b/ufund-ui/src/app/components/signup/signup.component.css index 2a10016..d4ea97b 100644 --- a/ufund-ui/src/app/components/signup/signup.component.css +++ b/ufund-ui/src/app/components/signup/signup.component.css @@ -14,3 +14,11 @@ background-color: white; box-shadow: 0 0 10px 10px black; } + +#bar { + width: 100%; + height: 10px; + padding: 0; + margin: 0; +} + diff --git a/ufund-ui/src/app/components/signup/signup.component.html b/ufund-ui/src/app/components/signup/signup.component.html index 742b8cf..5b1b4f7 100644 --- a/ufund-ui/src/app/components/signup/signup.component.html +++ b/ufund-ui/src/app/components/signup/signup.component.html @@ -2,6 +2,7 @@ + {{statusText | async}} {{strength | async}} Account created Proceed to login -- cgit v1.2.3 From 350a120eb0a578aa468b903a83f47168d6b8db13 Mon Sep 17 00:00:00 2001 From: Gunther6070 Date: Wed, 26 Mar 2025 19:20:26 -0400 Subject: Fixed expiration and authDAO test --- .../main/java/com/ufund/api/ufundapi/persistence/UserAuthFIleDAO.java | 2 +- .../java/com/ufund/api/ufundapi/persistence/UserAuthFileDAOTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserAuthFIleDAO.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserAuthFIleDAO.java index 7bda3f9..24a426b 100644 --- a/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserAuthFIleDAO.java +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserAuthFIleDAO.java @@ -37,7 +37,7 @@ public class UserAuthFIleDAO implements UserAuthDAO { UserAuth[] userAuthKeysArray = objectMapper.readValue(new File(filename), UserAuth[].class); for (UserAuth userAuth : userAuthKeysArray) { - if (userAuth.getExpiration().isBefore(LocalDateTime.now())) { + if (userAuth.getExpiration().isAfter(LocalDateTime.now())) { userAuthMap.put(userAuth.getKey(), userAuth); } } diff --git a/ufund-api/src/test/java/com/ufund/api/ufundapi/persistence/UserAuthFileDAOTest.java b/ufund-api/src/test/java/com/ufund/api/ufundapi/persistence/UserAuthFileDAOTest.java index 5e92deb..a4842c5 100644 --- a/ufund-api/src/test/java/com/ufund/api/ufundapi/persistence/UserAuthFileDAOTest.java +++ b/ufund-api/src/test/java/com/ufund/api/ufundapi/persistence/UserAuthFileDAOTest.java @@ -43,7 +43,7 @@ public class UserAuthFileDAOTest { String key = "123"; UserAuth auth = userAuthFIleDAO.getUserAuth(key); - assertEquals(auth, userAuths[0]); + assertEquals(userAuths[0], auth); } @Test -- cgit v1.2.3 From 7ccbfb414de47941c8acb2e6f9c2cc7a01cd819c Mon Sep 17 00:00:00 2001 From: Gunther6070 Date: Thu, 27 Mar 2025 18:47:07 -0400 Subject: Added requirements list, updated bar, and disabling of creation with weak passwords. --- .../src/app/components/signup/signup.component.css | 13 +++++++--- .../app/components/signup/signup.component.html | 10 +++++++- .../src/app/components/signup/signup.component.ts | 29 +++++++++++++++------- 3 files changed, 39 insertions(+), 13 deletions(-) diff --git a/ufund-ui/src/app/components/signup/signup.component.css b/ufund-ui/src/app/components/signup/signup.component.css index d4ea97b..799cbd2 100644 --- a/ufund-ui/src/app/components/signup/signup.component.css +++ b/ufund-ui/src/app/components/signup/signup.component.css @@ -17,8 +17,15 @@ #bar { width: 100%; - height: 10px; - padding: 0; - margin: 0; + height: 20px; + -webkit-appearance: none; + appearance: none; + border: none; + border-radius: 10px; + overflow: hidden; + background-color: red; } +#requirement2 { + color: red; +} diff --git a/ufund-ui/src/app/components/signup/signup.component.html b/ufund-ui/src/app/components/signup/signup.component.html index 5b1b4f7..1b50d39 100644 --- a/ufund-ui/src/app/components/signup/signup.component.html +++ b/ufund-ui/src/app/components/signup/signup.component.html @@ -1,8 +1,16 @@

Signup:

- + + +
{{test | async}}
+ + + + {{requirement}} + + {{statusText | async}} {{strength | async}} Account created Proceed to login diff --git a/ufund-ui/src/app/components/signup/signup.component.ts b/ufund-ui/src/app/components/signup/signup.component.ts index 48c6387..10fbce5 100644 --- a/ufund-ui/src/app/components/signup/signup.component.ts +++ b/ufund-ui/src/app/components/signup/signup.component.ts @@ -2,6 +2,7 @@ import { Component } from '@angular/core'; import {UsersService} from '../../services/users.service'; import {Router} from '@angular/router'; import {BehaviorSubject} from 'rxjs'; +import {Need} from '../../models/Need'; @Component({ selector: 'app-signup', @@ -13,8 +14,11 @@ export class SignupComponent { protected statusText = new BehaviorSubject("") protected showSuccessMessage = new BehaviorSubject(false) - protected passwordStrength = new BehaviorSubject("") + protected passwordStrongEnough = new BehaviorSubject(true) + passwordRequirements: String[] = [("❌ Password length"), ("❌ Lowercase letters")]; protected strength = new BehaviorSubject(0) + protected color = new BehaviorSubject("red") + protected test = new BehaviorSubject("Password does not meet requirements") constructor( protected usersService: UsersService, @@ -36,7 +40,12 @@ export class SignupComponent { } checkPasswordStrength(password: string) { + this.passwordRequirements = [("❌ Password length"), ("❌ Lowercase letters")]; + this.test.next("Password does not meet requirements") this.statusText.next("") + this.passwordStrongEnough.next(true) + this.color.next("red") + if (password.match(/[^!-~]/g)) { this.statusText.next("Invalid characters") return @@ -45,32 +54,34 @@ export class SignupComponent { let strength = 0; if (password.length > 6) { strength++ - console.log("Long") + this.passwordRequirements[0] = "✅ Password length" + this.color.next("green") } if (password.length > 12) { strength++ - console.log("Longer") } if (password.match(/[a-z]/g)) { strength++ - console.log("a") } - if (password.match(/[0-9]/g)) { + if (password.match(/[A-Z]/g)) { strength++ - console.log("0") } - if (password.match(/[A-Z]/g)) { + if (password.match(/[0-9]/g)) { strength++ - console.log("A") } if (password.match(/[!-/]/g)) { strength++ - console.log("!") + } + + if (strength >= 5) { + this.passwordStrongEnough.next(false) + this.test.next("") } this.strength.next(strength) } + protected readonly length = length; } let friendlyHttpStatus: {[key: number]: string} = { -- cgit v1.2.3 From 451a9abd5a4c461ccbb0b7b7d51b78dbfe12ec54 Mon Sep 17 00:00:00 2001 From: Gunther6070 Date: Thu, 27 Mar 2025 18:47:26 -0400 Subject: Added requirements list, updated bar, and disabling of creation with weak passwords. --- ufund-ui/src/app/components/signup/signup.component.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/ufund-ui/src/app/components/signup/signup.component.ts b/ufund-ui/src/app/components/signup/signup.component.ts index 10fbce5..9532e42 100644 --- a/ufund-ui/src/app/components/signup/signup.component.ts +++ b/ufund-ui/src/app/components/signup/signup.component.ts @@ -2,7 +2,6 @@ import { Component } from '@angular/core'; import {UsersService} from '../../services/users.service'; import {Router} from '@angular/router'; import {BehaviorSubject} from 'rxjs'; -import {Need} from '../../models/Need'; @Component({ selector: 'app-signup', -- cgit v1.2.3 From 4f5e9e9ecda282a98af5d70bd6cf0540973c7314 Mon Sep 17 00:00:00 2001 From: sowgro Date: Thu, 27 Mar 2025 18:47:27 -0400 Subject: Remove crutch --- .../com/ufund/api/ufundapi/persistence/UserFileDAO.java | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserFileDAO.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserFileDAO.java index 4b09449..ec94da8 100644 --- a/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserFileDAO.java +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/persistence/UserFileDAO.java @@ -2,7 +2,6 @@ package com.ufund.api.ufundapi.persistence; import java.io.File; import java.io.IOException; -import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.Objects; @@ -81,16 +80,8 @@ public class UserFileDAO implements UserDAO { public User updateUser(User user) throws IOException { synchronized (users) { if (users.containsKey(user.getUsername())) { - if (user.getBasket() == null || user.getType() == null) { - System.err.println("CRUTCH HAPPENED"); - User oldData = users.get(user.getUsername()); - User crutch = new User(oldData.getUsername(), 0, new ArrayList<>(), oldData.getType()); - crutch.copyPassword(oldData); - users.put(user.getUsername(), crutch); - } else { - var old = users.put(user.getUsername(), user); - user.copyPassword(Objects.requireNonNull(old)); - } + var old = users.put(user.getUsername(), user); + user.copyPassword(Objects.requireNonNull(old)); save(); return user; } else { -- cgit v1.2.3 From 785d0df231d0cfdbf63f5ed60b56fb882f694725 Mon Sep 17 00:00:00 2001 From: Gunther6070 Date: Sat, 29 Mar 2025 15:28:59 -0400 Subject: Finished implementing signup page and checked for the majority of edge cases. --- .../src/app/components/signup/signup.component.css | 23 +++++-- .../app/components/signup/signup.component.html | 23 +++---- .../src/app/components/signup/signup.component.ts | 80 ++++++++++++++++------ 3 files changed, 85 insertions(+), 41 deletions(-) diff --git a/ufund-ui/src/app/components/signup/signup.component.css b/ufund-ui/src/app/components/signup/signup.component.css index 799cbd2..2fa5051 100644 --- a/ufund-ui/src/app/components/signup/signup.component.css +++ b/ufund-ui/src/app/components/signup/signup.component.css @@ -16,16 +16,27 @@ } #bar { + height: 5px; width: 100%; - height: 20px; - -webkit-appearance: none; appearance: none; - border: none; - border-radius: 10px; overflow: hidden; - background-color: red; + margin-top: -5px; } -#requirement2 { +#bar::-webkit-progress-bar { + background-color: lightgray; + transition: width 0.5s ease-in-out, background-color 0.5s ease-in-out; +} + +.color0::-webkit-progress-value { background: rgba(255, 0 ,0, 1); transition: background-color 0.5s ease-in-out, width 0.5s ease-in-out; } +.color1::-webkit-progress-value { background: rgba(255, 0 ,0, 1); transition: background-color 0.5s ease-in-out, width 0.5s ease-in-out; } +.color2::-webkit-progress-value { background: rgba(255, 165, 0, 1); transition: background-color 0.5s ease-in-out, width 0.5s ease-in-out; } +.color3::-webkit-progress-value { background: rgba(255, 255, 0, 1); transition: background-color 0.5s ease-in-out, width 0.5s ease-in-out; } +.color4::-webkit-progress-value { background: rgba(173, 255, 47, 1); transition: background-color 0.5s ease-in-out, width 0.5s ease-in-out; } +.color5::-webkit-progress-value { background: rgba(50, 205, 50, 1); transition: background-color 0.5s ease-in-out, width 0.5s ease-in-out; } +.color6::-webkit-progress-value { background: rgba(0, 128, 0, 1); transition: background-color 0.5s ease-in-out, width 0.5s ease-in-out; } + +#requirement2, #statusText, #passwordStatusText, #usernameStatusText { color: red; } + diff --git a/ufund-ui/src/app/components/signup/signup.component.html b/ufund-ui/src/app/components/signup/signup.component.html index 1b50d39..e078123 100644 --- a/ufund-ui/src/app/components/signup/signup.component.html +++ b/ufund-ui/src/app/components/signup/signup.component.html @@ -1,16 +1,13 @@

Signup:

- - - - -
{{test | async}}
- - - - - {{requirement}} + +{{usernameStatusText | async}} + + +{{statusText | async}} + + {{requirement.title}} - -{{statusText | async}} -{{strength | async}} + +{{passwordStatusText | async}} + Account created Proceed to login diff --git a/ufund-ui/src/app/components/signup/signup.component.ts b/ufund-ui/src/app/components/signup/signup.component.ts index 9532e42..b3432e6 100644 --- a/ufund-ui/src/app/components/signup/signup.component.ts +++ b/ufund-ui/src/app/components/signup/signup.component.ts @@ -1,23 +1,34 @@ -import { Component } from '@angular/core'; +import {Component, ElementRef, ViewChild} from '@angular/core'; import {UsersService} from '../../services/users.service'; import {Router} from '@angular/router'; import {BehaviorSubject} from 'rxjs'; +class PasswordRequirements { + sixLong: {title: string, value: boolean} = {title: 'Is 6 characters or longer', value: false} + twelveLong: {title: string, value: boolean} = {title: 'Is 12 characters or longer', value: false} + lowercase: {title: string, value: boolean} = {title: 'Includes lowercase letter', value: false} + uppercase: {title: string, value: boolean} = {title: 'Includes uppercase letter', value: false} + number: {title: string, value: boolean} = {title: 'Includes number' , value: false} + symbol: {title: string, value: boolean} = {title: 'Includes symbol' , value: false} +} + @Component({ selector: 'app-signup', standalone: false, templateUrl: './signup.component.html', styleUrl: './signup.component.css' }) + export class SignupComponent { protected statusText = new BehaviorSubject("") + protected passwordStatusText = new BehaviorSubject("") + protected usernameStatusText = new BehaviorSubject("") protected showSuccessMessage = new BehaviorSubject(false) - protected passwordStrongEnough = new BehaviorSubject(true) - passwordRequirements: String[] = [("❌ Password length"), ("❌ Lowercase letters")]; + protected passwordStrongEnough = new BehaviorSubject(false) + protected ableToCreateAccount = new BehaviorSubject(false) + protected passwordRequirements: PasswordRequirements = new PasswordRequirements() protected strength = new BehaviorSubject(0) - protected color = new BehaviorSubject("red") - protected test = new BehaviorSubject("Password does not meet requirements") constructor( protected usersService: UsersService, @@ -38,49 +49,74 @@ export class SignupComponent { }) } + comparePassword(username: string, passConfirm:string, password: string) { + this.passwordStatusText.next("") + this.usernameStatusText.next("") + this.checkPasswordStrength(password); + + if (username === "") { + this.usernameStatusText.next("Username field can't be blank") + } + + if (!(password === passConfirm) && !!passConfirm) { + this.passwordStatusText.next("Passwords don't match") + } + this.ableToCreateAccount.next(this.passwordStrongEnough.getValue() && password === passConfirm && !!username) + } + checkPasswordStrength(password: string) { - this.passwordRequirements = [("❌ Password length"), ("❌ Lowercase letters")]; - this.test.next("Password does not meet requirements") - this.statusText.next("") - this.passwordStrongEnough.next(true) - this.color.next("red") + this.strength.next(0) + this.passwordRequirements = new PasswordRequirements() + this.passwordStrongEnough.next(false) if (password.match(/[^!-~]/g)) { this.statusText.next("Invalid characters") + return } - let strength = 0; if (password.length > 6) { - strength++ - this.passwordRequirements[0] = "✅ Password length" - this.color.next("green") + this.passwordRequirements.sixLong.value = true } if (password.length > 12) { - strength++ + this.passwordRequirements.twelveLong.value = true } if (password.match(/[a-z]/g)) { - strength++ + this.passwordRequirements.lowercase.value = true } if (password.match(/[A-Z]/g)) { - strength++ + this.passwordRequirements.uppercase.value = true } if (password.match(/[0-9]/g)) { - strength++ + this.passwordRequirements.number.value = true } - if (password.match(/[!-/]/g)) { - strength++ + if (password.match(/[^A-Za-z0-9]/g)) { + this.passwordRequirements.symbol.value = true } + let strength = 0 + Object.values(this.passwordRequirements).forEach(k => { + k.value && strength++ + }) + if (strength >= 5) { - this.passwordStrongEnough.next(false) - this.test.next("") + this.passwordStrongEnough.next(true) + this.statusText.next("") + } else if (strength == 0) { + this.statusText.next("") + } else { + this.statusText.next("Password does not meet requirements") } this.strength.next(strength) } + getColor() { + return `rgba(${(this.strength.getValue()/7) * 255}, ${255 - (this.strength.getValue()/7) * 255}, 0)` + } + protected readonly length = length; + protected readonly Object = Object; } let friendlyHttpStatus: {[key: number]: string} = { -- cgit v1.2.3 From b539c504782072fe933668b893c708bf577443dd Mon Sep 17 00:00:00 2001 From: Gunther6070 Date: Sat, 29 Mar 2025 15:29:16 -0400 Subject: Finished implementing signup page and checked for the majority of edge cases. --- ufund-ui/src/app/components/signup/signup.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ufund-ui/src/app/components/signup/signup.component.ts b/ufund-ui/src/app/components/signup/signup.component.ts index b3432e6..383d6a7 100644 --- a/ufund-ui/src/app/components/signup/signup.component.ts +++ b/ufund-ui/src/app/components/signup/signup.component.ts @@ -1,4 +1,4 @@ -import {Component, ElementRef, ViewChild} from '@angular/core'; +import {Component} from '@angular/core'; import {UsersService} from '../../services/users.service'; import {Router} from '@angular/router'; import {BehaviorSubject} from 'rxjs'; -- cgit v1.2.3 From 41c92354536d5545dee97e368b3a4b3c25c1a77f Mon Sep 17 00:00:00 2001 From: Gunther6070 Date: Sat, 29 Mar 2025 15:47:46 -0400 Subject: Added emojis before requirements --- .../src/app/components/signup/signup.component.html | 8 ++++++++ ufund-ui/src/app/components/signup/signup.component.ts | 18 ++++++++++++------ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/ufund-ui/src/app/components/signup/signup.component.html b/ufund-ui/src/app/components/signup/signup.component.html index e078123..e282a9c 100644 --- a/ufund-ui/src/app/components/signup/signup.component.html +++ b/ufund-ui/src/app/components/signup/signup.component.html @@ -1,13 +1,21 @@

Signup:

{{usernameStatusText | async}} + + + {{statusText | async}} + {{requirement.title}} + + {{passwordStatusText | async}} + + Account created Proceed to login diff --git a/ufund-ui/src/app/components/signup/signup.component.ts b/ufund-ui/src/app/components/signup/signup.component.ts index 383d6a7..60f4098 100644 --- a/ufund-ui/src/app/components/signup/signup.component.ts +++ b/ufund-ui/src/app/components/signup/signup.component.ts @@ -4,12 +4,12 @@ import {Router} from '@angular/router'; import {BehaviorSubject} from 'rxjs'; class PasswordRequirements { - sixLong: {title: string, value: boolean} = {title: 'Is 6 characters or longer', value: false} - twelveLong: {title: string, value: boolean} = {title: 'Is 12 characters or longer', value: false} - lowercase: {title: string, value: boolean} = {title: 'Includes lowercase letter', value: false} - uppercase: {title: string, value: boolean} = {title: 'Includes uppercase letter', value: false} - number: {title: string, value: boolean} = {title: 'Includes number' , value: false} - symbol: {title: string, value: boolean} = {title: 'Includes symbol' , value: false} + sixLong: {title: string, value: boolean} = {title: '❌ Is 6 characters or longer', value: false} + twelveLong: {title: string, value: boolean} = {title: '❌ Is 12 characters or longer', value: false} + lowercase: {title: string, value: boolean} = {title: '❌ Includes lowercase letter', value: false} + uppercase: {title: string, value: boolean} = {title: '❌ Includes uppercase letter', value: false} + number: {title: string, value: boolean} = {title: '❌ Includes number' , value: false} + symbol: {title: string, value: boolean} = {title: '❌ Includes symbol' , value: false} } @Component({ @@ -76,21 +76,27 @@ export class SignupComponent { } if (password.length > 6) { + this.passwordRequirements.sixLong.title = '✅ Is 6 characters or longer' this.passwordRequirements.sixLong.value = true } if (password.length > 12) { + this.passwordRequirements.twelveLong.title = '✅ Is 12 characters or longer' this.passwordRequirements.twelveLong.value = true } if (password.match(/[a-z]/g)) { + this.passwordRequirements.lowercase.title = '✅ Includes lowercase letter' this.passwordRequirements.lowercase.value = true } if (password.match(/[A-Z]/g)) { + this.passwordRequirements.uppercase.title = '✅ Includes uppercase letter' this.passwordRequirements.uppercase.value = true } if (password.match(/[0-9]/g)) { + this.passwordRequirements.number.title = '✅ Includes number' this.passwordRequirements.number.value = true } if (password.match(/[^A-Za-z0-9]/g)) { + this.passwordRequirements.symbol.title = '✅ Includes symbol' this.passwordRequirements.symbol.value = true } -- cgit v1.2.3 From 3c9a9004780c0b91772fd7f868c642bdadb60348 Mon Sep 17 00:00:00 2001 From: sowgro Date: Sun, 30 Mar 2025 14:10:09 -0400 Subject: Clean up signup component --- .../src/app/components/login/login.component.html | 2 +- .../src/app/components/signup/signup.component.css | 11 ++++-- .../app/components/signup/signup.component.html | 45 ++++++++++++---------- .../src/app/components/signup/signup.component.ts | 35 ++++++++--------- 4 files changed, 49 insertions(+), 44 deletions(-) diff --git a/ufund-ui/src/app/components/login/login.component.html b/ufund-ui/src/app/components/login/login.component.html index 2cdb6d0..a6441f4 100644 --- a/ufund-ui/src/app/components/login/login.component.html +++ b/ufund-ui/src/app/components/login/login.component.html @@ -3,5 +3,5 @@ - + {{statusText | async}} diff --git a/ufund-ui/src/app/components/signup/signup.component.css b/ufund-ui/src/app/components/signup/signup.component.css index 2fa5051..f286cf9 100644 --- a/ufund-ui/src/app/components/signup/signup.component.css +++ b/ufund-ui/src/app/components/signup/signup.component.css @@ -1,8 +1,13 @@ -:host, .border { +:host { display: flex; flex-direction: column; max-width: 300px; - gap: 5px + gap: 10px; + + & > div { + display: flex; + flex-direction: column; + } } .border { @@ -20,7 +25,7 @@ width: 100%; appearance: none; overflow: hidden; - margin-top: -5px; + /*margin-top: -5px;*/ } #bar::-webkit-progress-bar { diff --git a/ufund-ui/src/app/components/signup/signup.component.html b/ufund-ui/src/app/components/signup/signup.component.html index e282a9c..ebedc2a 100644 --- a/ufund-ui/src/app/components/signup/signup.component.html +++ b/ufund-ui/src/app/components/signup/signup.component.html @@ -1,21 +1,26 @@

Signup:

- -{{usernameStatusText | async}} - - - - - -{{statusText | async}} - - - {{requirement.title}} - - - - -{{passwordStatusText | async}} - - - -Account created Proceed to login +
+ + {{usernameStatusText | async}} +
+ +
+ + + {{passwordStatusText | async}} + + + {{requirement.title}} + +
+ +
+ + {{confirmPassStatusText | async}} +
+ +
+ + Account created Proceed to login + {{statusText | async}} +
diff --git a/ufund-ui/src/app/components/signup/signup.component.ts b/ufund-ui/src/app/components/signup/signup.component.ts index 60f4098..3b43287 100644 --- a/ufund-ui/src/app/components/signup/signup.component.ts +++ b/ufund-ui/src/app/components/signup/signup.component.ts @@ -4,12 +4,12 @@ import {Router} from '@angular/router'; import {BehaviorSubject} from 'rxjs'; class PasswordRequirements { - sixLong: {title: string, value: boolean} = {title: '❌ Is 6 characters or longer', value: false} - twelveLong: {title: string, value: boolean} = {title: '❌ Is 12 characters or longer', value: false} - lowercase: {title: string, value: boolean} = {title: '❌ Includes lowercase letter', value: false} - uppercase: {title: string, value: boolean} = {title: '❌ Includes uppercase letter', value: false} - number: {title: string, value: boolean} = {title: '❌ Includes number' , value: false} - symbol: {title: string, value: boolean} = {title: '❌ Includes symbol' , value: false} + sixLong: {title: string, value: boolean} = {title: 'Is 6 characters or longer' , value: false} + twelveLong: {title: string, value: boolean} = {title: 'Is 12 characters or longer', value: false} + lowercase: {title: string, value: boolean} = {title: 'Includes lowercase letter' , value: false} + uppercase: {title: string, value: boolean} = {title: 'Includes uppercase letter' , value: false} + number: {title: string, value: boolean} = {title: 'Includes number' , value: false} + symbol: {title: string, value: boolean} = {title: 'Includes symbol' , value: false} } @Component({ @@ -21,14 +21,15 @@ class PasswordRequirements { export class SignupComponent { - protected statusText = new BehaviorSubject("") protected passwordStatusText = new BehaviorSubject("") + protected confirmPassStatusText = new BehaviorSubject("") protected usernameStatusText = new BehaviorSubject("") protected showSuccessMessage = new BehaviorSubject(false) protected passwordStrongEnough = new BehaviorSubject(false) protected ableToCreateAccount = new BehaviorSubject(false) protected passwordRequirements: PasswordRequirements = new PasswordRequirements() protected strength = new BehaviorSubject(0) + protected statusText = new BehaviorSubject(""); constructor( protected usersService: UsersService, @@ -49,8 +50,8 @@ export class SignupComponent { }) } - comparePassword(username: string, passConfirm:string, password: string) { - this.passwordStatusText.next("") + validate(username: string, passConfirm:string, password: string) { + this.confirmPassStatusText.next("") this.usernameStatusText.next("") this.checkPasswordStrength(password); @@ -59,7 +60,7 @@ export class SignupComponent { } if (!(password === passConfirm) && !!passConfirm) { - this.passwordStatusText.next("Passwords don't match") + this.confirmPassStatusText.next("Passwords don't match") } this.ableToCreateAccount.next(this.passwordStrongEnough.getValue() && password === passConfirm && !!username) } @@ -70,33 +71,27 @@ export class SignupComponent { this.passwordStrongEnough.next(false) if (password.match(/[^!-~]/g)) { - this.statusText.next("Invalid characters") + this.passwordStatusText.next("Invalid characters") return } if (password.length > 6) { - this.passwordRequirements.sixLong.title = '✅ Is 6 characters or longer' this.passwordRequirements.sixLong.value = true } if (password.length > 12) { - this.passwordRequirements.twelveLong.title = '✅ Is 12 characters or longer' this.passwordRequirements.twelveLong.value = true } if (password.match(/[a-z]/g)) { - this.passwordRequirements.lowercase.title = '✅ Includes lowercase letter' this.passwordRequirements.lowercase.value = true } if (password.match(/[A-Z]/g)) { - this.passwordRequirements.uppercase.title = '✅ Includes uppercase letter' this.passwordRequirements.uppercase.value = true } if (password.match(/[0-9]/g)) { - this.passwordRequirements.number.title = '✅ Includes number' this.passwordRequirements.number.value = true } if (password.match(/[^A-Za-z0-9]/g)) { - this.passwordRequirements.symbol.title = '✅ Includes symbol' this.passwordRequirements.symbol.value = true } @@ -107,11 +102,11 @@ export class SignupComponent { if (strength >= 5) { this.passwordStrongEnough.next(true) - this.statusText.next("") + this.passwordStatusText.next("") } else if (strength == 0) { - this.statusText.next("") + this.passwordStatusText.next("") } else { - this.statusText.next("Password does not meet requirements") + this.passwordStatusText.next("Password does not meet requirements") } this.strength.next(strength) -- cgit v1.2.3