From d7fb8ffb9129a2c7695e9f26f63915454cf7bd58 Mon Sep 17 00:00:00 2001 From: Dylan De Faoite Date: Thu, 1 Oct 2026 21:56:02 +0100 Subject: [PATCH] add refresh token tables & entities --- .../ddf/vodsystem/entities/RefreshToken.java | 35 +++++++++++++++++++ .../ddf/vodsystem/entities/TokenFamily.java | 25 +++++++++++++ .../db/migrations/V2__refresh_tokens.sql | 31 ++++++++++++++++ 3 files changed, 91 insertions(+) create mode 100644 src/main/java/com/ddf/vodsystem/entities/RefreshToken.java create mode 100644 src/main/java/com/ddf/vodsystem/entities/TokenFamily.java create mode 100644 src/main/resources/db/migrations/V2__refresh_tokens.sql diff --git a/src/main/java/com/ddf/vodsystem/entities/RefreshToken.java b/src/main/java/com/ddf/vodsystem/entities/RefreshToken.java new file mode 100644 index 0000000..4e9a32e --- /dev/null +++ b/src/main/java/com/ddf/vodsystem/entities/RefreshToken.java @@ -0,0 +1,35 @@ +package com.ddf.vodsystem.entities; + +import jakarta.persistence.*; +import lombok.Data; + +import java.time.Instant; + +@Entity +@Table(name = "refresh_tokens") +@Data +public class RefreshToken { + @Id + @Column(name = "id", nullable = false, unique = true) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "family_id", nullable = false) + private TokenFamily tokenFamily; + + @Column(name = "token_hash", unique = true, nullable = false) + private byte[] tokenHash; + + @Column(name = "expires_at", nullable = false) + private Instant expiresAt; + + @Column(name = "used_at") + private Instant usedAt; + + @Column(name = "revoked_at") + private Instant revokedAt; +} diff --git a/src/main/java/com/ddf/vodsystem/entities/TokenFamily.java b/src/main/java/com/ddf/vodsystem/entities/TokenFamily.java new file mode 100644 index 0000000..407bd16 --- /dev/null +++ b/src/main/java/com/ddf/vodsystem/entities/TokenFamily.java @@ -0,0 +1,25 @@ +package com.ddf.vodsystem.entities; + +import jakarta.persistence.*; +import lombok.Data; + +import java.time.Instant; + +@Entity +@Table(name = "token_families") +@Data +public class TokenFamily { + @Id + @Column(name = "id", nullable = false, unique = true) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; + + @Column(name = "expires_at", nullable = false) + private Instant expiresAt; + + @Column(name = "revoked_at") + private Instant revokedAt; +} diff --git a/src/main/resources/db/migrations/V2__refresh_tokens.sql b/src/main/resources/db/migrations/V2__refresh_tokens.sql new file mode 100644 index 0000000..bb20b1d --- /dev/null +++ b/src/main/resources/db/migrations/V2__refresh_tokens.sql @@ -0,0 +1,31 @@ +CREATE TABLE IF NOT EXISTS token_families +( + id BIGSERIAL PRIMARY KEY NOT NULl, + user_id BIGSERIAL NOT NULL, + expires_at TIMESTAMPTZ NOT NULL, + revoked_at TIMESTAMPTZ, + + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE +); + +CREATE INDEX IF NOT EXISTS idx_token_families_user_id + ON token_families (user_id); + +CREATE TABLE IF NOT EXISTS refresh_tokens +( + id BIGSERIAL PRIMARY KEY NOT NULL, + user_id BIGSERIAL NOT NULL, + family_id BIGSERIAL NOT NULL, + token_hash BYTEA NOT NULL, + expires_at TIMESTAMPTZ NOT NULL, + used_at TIMESTAMPTZ, + revoked_at TIMESTAMPTZ, + + CONSTRAINT refresh_tokens_hash_len CHECK (octet_length(token_hash) = 32), + + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY (family_id) REFERENCES token_families(id) ON DELETE CASCADE +); + +CREATE INDEX IF NOT EXISTS idx_refresh_tokens_family_id + ON refresh_tokens (family_id);