Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
95 commits
Select commit Hold shift + click to select a range
823c86b
made wakeTimeHour to localtime to support minutes
Thiies Jun 5, 2025
c70dc34
chore: update openapi types
Thiies Jun 5, 2025
254316c
created daos
Thiies Jun 5, 2025
64acbdb
impl jwt in pom
Thiies Jun 7, 2025
0d1aa4c
implemented dtos, daos and db logic
Thiies Jun 7, 2025
3bbda54
auth endpoints working
Thiies Jun 7, 2025
ffb4086
implemented in spring security
Thiies Jun 7, 2025
9ba0642
implemented GET /groups/user
Thiies Jun 7, 2025
3a732bb
made endpoint name static
Thiies Jun 7, 2025
f8ce8cb
added leave group endpoint
Thiies Jun 7, 2025
340678e
renamed leave endpoint
Thiies Jun 7, 2025
34c4314
implemented createdBy
Thiies Jun 7, 2025
dfd69d4
auth for every /groups path. excluding some endpoints
Thiies Jun 8, 2025
c3aae06
implemented group member dto + creating group member on group creation
Thiies Jun 8, 2025
9b121db
add createdBy to rules
Thiies Jun 8, 2025
26de012
copying createdBy from old season
Thiies Jun 8, 2025
0f2acfd
saving createdBy for seasons
Thiies Jun 8, 2025
fc65709
saving for matches
Thiies Jun 8, 2025
13bb72c
using seasondto instead of season dao
Thiies Jun 8, 2025
5409fa6
use many-to-one relation
Thiies Jun 8, 2025
9780bdb
soft delete group members
Thiies Jun 8, 2025
1950088
join group endpoint
Thiies Jun 8, 2025
a994137
fixed tests + made auth controller post endpoints
Thiies Jun 8, 2025
fb8631f
loading jwt secret via env variables
Thiies Jun 8, 2025
98550ff
fix: add JWT_TOKEN to pass tests in ci
Laurin-Notemann Jun 8, 2025
4655996
fix
Laurin-Notemann Jun 8, 2025
d7b6aa9
fix: this time
Laurin-Notemann Jun 8, 2025
1eacd24
lsot
Laurin-Notemann Jun 8, 2025
3b4c1b8
Merge 1eacd240de3e1b16ac6c6eb81fd8af1ade2ed61a into 18e1067e3dfe29994…
Thiies Jun 8, 2025
51ebd51
chore: update openapi types
Laurin-Notemann Jun 8, 2025
237e176
added todo
Thiies Jun 9, 2025
02d7bd0
add http request asserts
Thiies Jun 10, 2025
af12bc6
better create test
Thiies Jun 10, 2025
ee943b0
more create tests
Thiies Jun 10, 2025
3d9df54
more tests
Thiies Jun 10, 2025
9337b17
moreee tests
Thiies Jun 10, 2025
47a5712
group join tests
Thiies Jun 10, 2025
7f20ed4
group tests complete
Thiies Jun 10, 2025
5e7b35c
fixed group tests
Thiies Jun 10, 2025
0c25563
added createTestGroup methods
Thiies Jun 12, 2025
12000b6
added requestutils + start season tests
Thiies Jun 12, 2025
a29b36c
season tests
Thiies Jun 12, 2025
82fef9f
changed season update to use dto
Thiies Jun 12, 2025
578748b
fixed incomplete season update dto
Thiies Jun 12, 2025
b2f5a81
Merge remote-tracking branch 'origin/feat/wake-time-minutes' into fea…
Thiies Jun 12, 2025
ac178a3
impl new waketime in tests and code
Thiies Jun 12, 2025
5ccfcb5
removed some todos
Thiies Jun 12, 2025
aecb18c
finished season tests
Thiies Jun 12, 2025
d3c5300
testing for valid userId in created + rule move tests
Thiies Jun 12, 2025
d7ad011
changed rule move creation
Thiies Jun 12, 2025
0b3b5d9
rule move tests
Thiies Jun 12, 2025
b8557f0
added test for creation of rule moves on new season
Thiies Jun 12, 2025
fd8cb62
dont create beerpong rules for other presets
Thiies Jun 12, 2025
be9f33e
rule copy test
Thiies Jun 12, 2025
c4bdd65
rule tests
Thiies Jun 12, 2025
a8af6ca
profiles test
Thiies Jun 12, 2025
09985f4
profile tests
Thiies Jun 12, 2025
aa9c2e7
player tests
Thiies Jun 12, 2025
fb3a6aa
player delete tests
Thiies Jun 12, 2025
60100e3
test copying of players on season start
Thiies Jun 12, 2025
9d31980
player tests
Thiies Jun 12, 2025
bde3cd5
base of match tests
Thiies Jun 12, 2025
a5999f7
create tests
Thiies Jun 13, 2025
18412b4
fix tests
Thiies Jun 13, 2025
77f0ddb
force team amount
Thiies Jun 13, 2025
c3dac42
invalid create tests
Thiies Jun 13, 2025
418241f
fix tests
Thiies Jun 13, 2025
f898a4a
remove todos
Thiies Jun 13, 2025
208d132
more match tests
Thiies Jun 13, 2025
756f01e
match overview test
Thiies Jun 15, 2025
7bc664d
overview invalid tests
Thiies Jun 15, 2025
449b4d5
overview all tests
Thiies Jun 15, 2025
7e0b4df
remove old match test
Thiies Jun 15, 2025
cf1e7dc
group test descr
Thiies Jun 15, 2025
423d856
add team size check to match update
Thiies Jun 15, 2025
7009e53
match update invalid tests
Thiies Jun 15, 2025
b56a36a
delete test
Thiies Jun 15, 2025
8dcb051
invalid match delete tests
Thiies Jun 15, 2025
e516568
match update test
Thiies Jun 15, 2025
dee01e6
leaderboard tests
Thiies Jun 15, 2025
e8fd529
remove useless boolean
Thiies Jun 15, 2025
ff90a27
first leaderboard tests
Thiies Jun 17, 2025
8d8c5e0
fix tests
Thiies Jun 17, 2025
4ef4cc8
Merge remote-tracking branch 'origin/staging' into feat/accounts
Thiies Sep 23, 2025
145652a
fix merge
Thiies Sep 23, 2025
9ec3c19
add group tests
Thiies Sep 23, 2025
de6b356
fix tests
Thiies Sep 24, 2025
aa51535
disabled elo test in normal test runtime
Thiies Sep 24, 2025
ce2887e
Merge remote-tracking branch 'origin/staging' into feat/accounts
Thiies Sep 24, 2025
6daa226
feat(frontend): accounts
LinusBolls Sep 26, 2025
4dd7b86
devops: force openapi action to rerun
LinusBolls Sep 26, 2025
c6a983e
Merge 4dd7b8685b6c1b408dfc39b184bff801b6267b47 into 1979c471176254ed5…
LinusBolls Sep 26, 2025
61df6d1
chore: update openapi types
LinusBolls Sep 26, 2025
2838fa4
feat(frontend): auth
LinusBolls Sep 26, 2025
fcbd9bc
devops: re-disable openapi action
LinusBolls Sep 26, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ POSTGRES_HOST=localhost
POSTGRES_PORT=5432

# backend
API_BASE_URL=http://localhost:8080
API_BASE_URL=http://localhost:8080/
JWT_SECRET=SomeVeryLongRandomSecretKeyWhichIsUsedToEncodeJwtTokensForAuthorization

# sentry
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/api-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,5 @@ jobs:
AWS_ENDPOINT: ${{ secrets.AWS_ENDPOINT }}
AWS_ACCESS_KEY: ${{ secrets.AWS_ACCESS_KEY }}
AWS_SECRET_KEY: ${{ secrets.AWS_SECRET_KEY }}
JWT_SECRET: justforthisrunnerthishasnovaluelalalalalalalalalalalalalalalalalalalalalalalalalalla
working-directory: api
2 changes: 2 additions & 0 deletions .github/workflows/generate-openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ jobs:
AWS_ENDPOINT: ${{ secrets.AWS_ENDPOINT }}
AWS_ACCESS_KEY: ${{ secrets.AWS_ACCESS_KEY }}
AWS_SECRET_KEY: ${{ secrets.AWS_SECRET_KEY }}
JWT_SECRET: justforthisrunnerthishasnovaluelalalalalalalalalalalalalalalalalalalalalalalalalalla
working-directory: api

# replace all occurences of "*/*" with "application/json" in the openapi.json file
Expand Down Expand Up @@ -130,3 +131,4 @@ jobs:
if: steps.check_authors.outputs.has_bot_author != 'true'
with:
commit_message: "chore: update openapi types"
skip_checkout: true
66 changes: 41 additions & 25 deletions api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,17 @@
<mapstruct.version>1.6.3</mapstruct.version>
</properties>
<dependencies>
<!-- SPRING -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.6.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
Expand All @@ -30,22 +41,9 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>

<!-- SENTRY -->
<dependency>
<groupId>io.sentry</groupId>
<artifactId>sentry-spring-boot-starter-jakarta</artifactId>
<version>7.8.0</version>
</dependency>
<dependency>
<groupId>io.sentry</groupId>
<artifactId>sentry-logback</artifactId>
<version>7.8.0</version>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

<!-- DATABASE -->
Expand All @@ -65,11 +63,28 @@
<scope>test</scope>
</dependency>

<!-- SENTRY -->
<dependency>
<groupId>io.sentry</groupId>
<artifactId>sentry-spring-boot-starter-jakarta</artifactId>
<version>7.8.0</version>
</dependency>
<dependency>
<groupId>io.sentry</groupId>
<artifactId>sentry-logback</artifactId>
<version>7.8.0</version>
</dependency>

<!-- UTILS -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
Expand All @@ -80,27 +95,27 @@
<artifactId>lombok-mapstruct-binding</artifactId>
<version>0.2.0</version>
</dependency>

<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3</artifactId>
<version>2.33.9</version>
</dependency>

<!-- JWT -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.6.0</version>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>com.google.cloud.sql</groupId>
<artifactId>postgres-socket-factory</artifactId>
<version>1.11.0</version>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
</dependency>
</dependencies>

Expand Down Expand Up @@ -176,4 +191,5 @@
</plugin>
</plugins>
</build>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package pro.beerpong.api.auth;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import pro.beerpong.api.control.GroupController;
import pro.beerpong.api.service.AuthService;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;

import java.io.IOException;
import java.util.List;

@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private static final String GROUPS_PATTERN = "/groups/**";
private static final String GROUP_ID_PATTERN = "/groups/{groupId}/**";
private static final List<String> NO_VALIDATION_ENDPOINTS = List.of(
"/groups",
"/groups/" + GroupController.USER_GROUPS_ENDPOINT,
"/groups/{groupId}/" + GroupController.JOIN_GROUP_ENDPOINT
);

private final AuthService authService;
private final AntPathMatcher pathMatcher = new AntPathMatcher();

@Override
protected void doFilterInternal(HttpServletRequest req,
HttpServletResponse res,
FilterChain chain) throws ServletException, IOException {
var header = req.getHeader("Authorization");

if (!StringUtils.hasText(header) || !header.startsWith("Bearer ")) {
res.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
res.getWriter().write("Missing or invalid Authorization header!");
return;
}

var token = header.substring(7);
var user = authService.userFromAccessToken(token);

if (user == null) {
res.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
res.getWriter().write("Invalid access token or invalid user-id!");
return;
}

if (!isExcludedFromValidation(req) && !authService.hasAccessToGroup(user, extractGroupId(req))) {
res.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
res.getWriter().write("No access to this group!");
return;
}

var auth = new UsernamePasswordAuthenticationToken(user, null, List.of(new SimpleGrantedAuthority("ROLE_GROUP_MEMBER")));
SecurityContextHolder.getContext().setAuthentication(auth);

chain.doFilter(req, res);
}

@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
return !pathMatcher.match(GROUPS_PATTERN, request.getRequestURI());
}

private boolean isExcludedFromValidation(HttpServletRequest request) {
return NO_VALIDATION_ENDPOINTS.stream().anyMatch(s -> pathMatcher.match(s, request.getRequestURI()));
}

private String extractGroupId(HttpServletRequest request) {
return (pathMatcher.match(GROUP_ID_PATTERN, request.getRequestURI()) ?
pathMatcher.extractUriTemplateVariables(GROUP_ID_PATTERN, request.getRequestURI()).get("groupId") :
"");
}
}
66 changes: 66 additions & 0 deletions api/src/main/java/pro/beerpong/api/auth/JwtTokenProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package pro.beerpong.api.auth;

import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.security.Key;
import java.util.Date;

@Component
public class JwtTokenProvider {
private Key key;
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.access.expiration-ms}")
private long accessExpirationMs;

@PostConstruct
public void init() {
key = Keys.hmacShaKeyFor(secret.getBytes());
}

public String createRefreshToken(String userId) {
return Jwts.builder()
.setSubject(userId)
.claim("type", "refresh")
.signWith(key, SignatureAlgorithm.HS256)
.compact();
}

public String createAccessToken(String userId) {
Date now = new Date();
Date expiry = new Date(now.getTime() + accessExpirationMs);

return Jwts.builder()
.setSubject(userId)
.claim("type", "access")
.setIssuedAt(now)
.setExpiration(expiry)
.signWith(key, SignatureAlgorithm.HS256)
.compact();
}

public Jws<Claims> parseToken(String token) {
return Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token);
}

public Claims validateToken(String token, String type) {
try {
var claims = parseToken(token).getBody();

if (!claims.get("type", String.class).equals(type)) {
return null;
}

return claims;
} catch (JwtException | IllegalArgumentException e) {
return null;
}
}
}
33 changes: 33 additions & 0 deletions api/src/main/java/pro/beerpong/api/auth/SecurityConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package pro.beerpong.api.auth;

import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import pro.beerpong.api.service.AuthService;

@Configuration
@RequiredArgsConstructor
public class SecurityConfig {
private final AuthService authService;

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
var jwtFilter = new JwtAuthenticationFilter(authService);

http
.csrf(AbstractHttpConfigurer::disable)
.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/groups/{groupId}/**").authenticated()
.anyRequest().permitAll()
)
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);

return http.build();
}
}
40 changes: 40 additions & 0 deletions api/src/main/java/pro/beerpong/api/control/AuthController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package pro.beerpong.api.control;

import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import pro.beerpong.api.model.dto.*;
import pro.beerpong.api.service.AuthService;

@RestController
@RequestMapping("/auth")
@RequiredArgsConstructor
public class AuthController {
private final AuthService authService;

@PostMapping("signup")
public ResponseEntity<ResponseEnvelope<AuthTokenDto>> signup(@RequestBody AuthSignupDto authSignupDto) {
var result = authService.registerDevice(authSignupDto);

if (result == null) {
return ResponseEnvelope.notOk(ErrorCodes.AUTH_REGISTER_INVALID_DTO);
}

return ResponseEnvelope.ok(result);
}

@PostMapping("refresh")
public ResponseEntity<ResponseEnvelope<AuthTokenDto>> refreshAuth(@RequestBody AuthRefreshDto dto) {
if (dto.getRefreshToken() == null || dto.getRefreshToken().trim().isEmpty()) {
return ResponseEnvelope.notOk(ErrorCodes.AUTH_REFRESH_INVALID_DTO);
}

var result = authService.refreshAuth(dto);

if (result == null) {
return ResponseEnvelope.notOk(ErrorCodes.AUTH_REFRESH_INVALID_TOKEN);
}

return ResponseEnvelope.ok(result);
}
}
Loading
Loading