package com.lincfusion.udyamlinc.security;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/** Issues and reads the two token flavours: officer console and enterprise app. */
@Component
public class JwtUtil {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration:86400000}")
private long expirationMs;
private Key signingKey() {
return Keys.hmacShaKeyFor(secret.getBytes());
}
/**
* Bank console session — scope=BANK, pinned to one institution.
*
*
The institution is a signed claim rather than something the console
* sends with each call. A tenancy key the caller can type is not a tenancy
* key, and this is the one place that distinction is enforced for every
* request that follows.
*/
public String generateBankToken(String username, String role, String blocks,
Long institutionId, Long branchId) {
Map claims = new HashMap<>();
claims.put("realm", "UDYAM");
claims.put("scope", "BANK");
claims.put("role", role);
claims.put("blocks", blocks);
claims.put("institutionId", institutionId);
claims.put("branchId", branchId);
return build(claims, username);
}
/**
* NABARD session — scope=NABARD, deliberately carrying no institution.
*
* An apex user is not a super-user with every tenancy at once; they are a
* user entitled to aggregates and to no borrower row at all. The absent
* claim is what {@code TenantContext} reads to mean exactly that.
*/
public String generateApexToken(String username, String role, Long institutionId) {
Map claims = new HashMap<>();
claims.put("realm", "UDYAM");
claims.put("scope", "NABARD");
claims.put("role", role);
claims.put("institutionId", institutionId);
return build(claims, username);
}
/**
* Enterprise app session — scope=ENTERPRISE, pinned to one enterprise id so
* the owner can only ever read their own record.
*/
public String generateEnterpriseToken(String enterpriseId, String sector) {
Map claims = new HashMap<>();
claims.put("realm", "UDYAM");
claims.put("scope", "ENTERPRISE");
claims.put("role", "OWNER");
claims.put("enterpriseId", enterpriseId);
claims.put("sector", sector);
return build(claims, enterpriseId);
}
private String build(Map claims, String subject) {
long now = System.currentTimeMillis();
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(new Date(now))
.setExpiration(new Date(now + expirationMs))
.signWith(signingKey(), SignatureAlgorithm.HS256)
.compact();
}
public Claims parse(String token) {
return Jwts.parserBuilder().setSigningKey(signingKey()).build().parseClaimsJws(token).getBody();
}
public String subject(String token) {
return parse(token).getSubject();
}
public String scope(String token) {
return String.valueOf(parse(token).get("scope"));
}
public String role(String token) {
return String.valueOf(parse(token).get("role"));
}
public Long institutionId(String token) {
return longClaim(parse(token), "institutionId");
}
public Long branchId(String token) {
return longClaim(parse(token), "branchId");
}
public String enterpriseId(String token) {
Object value = parse(token).get("enterpriseId");
return value == null ? null : String.valueOf(value);
}
/** JWT numerics arrive as Integer or Long depending on magnitude. */
private Long longClaim(Claims claims, String name) {
Object value = claims.get(name);
if (value == null) return null;
if (value instanceof Number number) return number.longValue();
try {
return Long.valueOf(String.valueOf(value));
} catch (NumberFormatException e) {
return null;
}
}
public boolean isValid(String token) {
try {
return parse(token).getExpiration().after(new Date());
} catch (Exception e) {
return false;
}
}
}