| 1 | package com.rbctcsworld.ecommerce.auth; | |
| 2 | ||
| 3 | import com.rbctcsworld.ecommerce.common.SharedEnvironment; | |
| 4 | ||
| 5 | import io.jsonwebtoken.Jwts; | |
| 6 | import io.jsonwebtoken.security.Keys; | |
| 7 | import org.springframework.beans.factory.annotation.Value; | |
| 8 | import org.springframework.core.env.Environment; | |
| 9 | import org.springframework.stereotype.Service; | |
| 10 | ||
| 11 | import javax.crypto.SecretKey; | |
| 12 | import java.nio.charset.StandardCharsets; | |
| 13 | import java.util.Date; | |
| 14 | ||
| 15 | /** | |
| 16 | * Issues and verifies HS256 tokens. Only the e-mail (subject) is trusted from the token: the ROLE is read | |
| 17 | * from the database on every request (JwtFilter), so editing the token to "role: ADMIN" gives nothing. | |
| 18 | * | |
| 19 | * Verification rejects: a wrong/edited signature, "alg: none" (unsigned) tokens, expired tokens and | |
| 20 | * tokens from another issuer. | |
| 21 | */ | |
| 22 | @Service | |
| 23 | public class JwtService { | |
| 24 | ||
| 25 | public static final String ISSUER = "rbctcsworld-ecommerce"; | |
| 26 | /** The development default in application.yml. It is public (in Git), so it must never run in production. */ | |
| 27 | static final String DEV_DEFAULT_SECRET = "change-this-development-secret-key-which-must-be-long-enough-123456789"; | |
| 28 | ||
| 29 | private final String secret; | |
| 30 | private final long expirationMs; | |
| 31 | private final Environment env; | |
| 32 | private SecretKey key; | |
| 33 | ||
| 34 | public JwtService(@Value("${app.jwt.secret}") String secret, | |
| 35 | @Value("${app.jwt.expiration-ms}") long expirationMs, | |
| 36 | Environment env) { | |
| 37 | this.secret = secret; | |
| 38 | this.expirationMs = expirationMs; | |
| 39 | this.env = env; | |
| 40 |
1
1. <init> : removed call to com/rbctcsworld/ecommerce/auth/JwtService::validateSecret → KILLED |
validateSecret(); |
| 41 | } | |
| 42 | ||
| 43 | /** Fails the application start instead of running with an unsafe key. */ | |
| 44 | private void validateSecret() { | |
| 45 |
1
1. validateSecret : removed conditional - replaced equality check with false → KILLED |
byte[] bytes = secret == null ? new byte[0] : secret.getBytes(StandardCharsets.UTF_8); |
| 46 |
2
1. validateSecret : removed conditional - replaced comparison check with false → KILLED 2. validateSecret : changed conditional boundary → KILLED |
if (bytes.length < 32) { |
| 47 | throw new IllegalStateException("app.jwt.secret must be at least 32 bytes (256 bits) for HS256"); | |
| 48 | } | |
| 49 |
2
1. validateSecret : removed conditional - replaced equality check with false → KILLED 2. validateSecret : removed conditional - replaced equality check with false → KILLED |
if (SharedEnvironment.isShared(env) && DEV_DEFAULT_SECRET.equals(secret)) { |
| 50 | throw new IllegalStateException("Refusing to start: the public development JWT secret is used with the 'prod' profile" | |
| 51 | + " or the public demo." | |
| 52 | + " Set the JWT_SECRET environment variable."); | |
| 53 | } | |
| 54 | key = Keys.hmacShaKeyFor(bytes); | |
| 55 | } | |
| 56 | ||
| 57 | public String generate(String email) { | |
| 58 | long now = System.currentTimeMillis(); | |
| 59 |
1
1. generate : replaced return value with "" for com/rbctcsworld/ecommerce/auth/JwtService::generate → KILLED |
return Jwts.builder() |
| 60 | .issuer(ISSUER) | |
| 61 | .subject(email) | |
| 62 |
1
1. generate : Replaced long addition with subtraction → KILLED |
.issuedAt(new Date(now)) |
| 63 | .expiration(new Date(now + expirationMs)) | |
| 64 | .signWith(key) | |
| 65 | .compact(); | |
| 66 | } | |
| 67 | ||
| 68 | /** Lifetime of a new access token, sent to the client as expiresIn. */ | |
| 69 | public long expiresInSeconds() { | |
| 70 |
2
1. expiresInSeconds : Replaced long division with multiplication → KILLED 2. expiresInSeconds : replaced long return with 0 for com/rbctcsworld/ecommerce/auth/JwtService::expiresInSeconds → KILLED |
return expirationMs / 1000; |
| 71 | } | |
| 72 | ||
| 73 | /** Returns the e-mail of a valid token; throws for anything else (JwtFilter then treats the caller as anonymous). */ | |
| 74 | public String email(String token) { | |
| 75 |
1
1. email : replaced return value with "" for com/rbctcsworld/ecommerce/auth/JwtService::email → KILLED |
return Jwts.parser() |
| 76 | .verifyWith(key) | |
| 77 | .requireIssuer(ISSUER) | |
| 78 | .build() | |
| 79 | .parseSignedClaims(token) | |
| 80 | .getPayload() | |
| 81 | .getSubject(); | |
| 82 | } | |
| 83 | } | |
Mutations | ||
| 40 |
1.1 |
|
| 45 |
1.1 |
|
| 46 |
1.1 2.2 |
|
| 49 |
1.1 2.2 |
|
| 59 |
1.1 |
|
| 62 |
1.1 |
|
| 70 |
1.1 2.2 |
|
| 75 |
1.1 |