8000 Merge Develop into Master for Release by adityachandelgit · Pull Request #245 · adityachandelgit/BookLore · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Merge Develop into Master for Release #245

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import com.adityachandel.booklore.model.dto.BookLoreUser;
import com.adityachandel.booklore.model.dto.request.UserLoginRequest;
import com.adityachandel.booklore.model.entity.BookLoreUserEntity;
import com.adityachandel.booklore.model.entity.RefreshTokenEntity;
import com.adityachandel.booklore.repository.RefreshTokenRepository;
import com.adityachandel.booklore.repository.UserRepository;
import com.adityachandel.booklore.service.user.UserCreatorService;
import lombok.AllArgsConstructor;
Expand All @@ -14,6 +16,7 @@
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.Map;
import java.util.Optional;

Expand All @@ -23,6 +26,7 @@ public class AuthenticationService {

private final AppProperties appProperties;
private final UserRepository userRepository;
private final RefreshTokenRepository refreshTokenRepository;
private final UserCreatorService userCreatorService;
private final PasswordEncoder passwordEncoder;
private final JwtUtils jwtUtils;
Expand All @@ -33,7 +37,8 @@ public BookLoreUser getAuthenticatedUser() {
}

public ResponseEntity<Map<String, String>> loginUser(UserLoginRequest loginRequest) {
BookLoreUserEntity user = userRepository.findByUsername(loginRequest.getUsername()).orElseThrow(() -> ApiError.USER_NOT_FOUND.createException(loginRequest.getUsername()));
BookLoreUserEntity user = userRepository.findByUsername(loginRequest.getUsername())
.orElseThrow(() -> ApiError.USER_NOT_FOUND.createException(loginRequest.getUsername()));

if (!passwordEncoder.matches(loginRequest.getPassword(), user.getPasswordHash())) {
throw ApiError.INVALID_CREDENTIALS.createException();
Expand Down Expand Up @@ -63,24 +68,48 @@ public ResponseEntity<Map<String, String>> loginUser(BookLoreUserEntity user) {
String accessToken = jwtUtils.generateAccessToken(user);
String refreshToken = jwtUtils.generateRefreshToken(user);

RefreshTokenEntity refreshTokenEntity = RefreshTokenEntity.builder()
.user(user)
.token(refreshToken)
.expiryDate(new Date(System.currentTimeMillis() + jwtUtils.getRefreshTokenExpirationMs()))
.revoked(false)
.build();

refreshTokenRepository.save(refreshTokenEntity);

return ResponseEntity.ok(Map.of(
"accessToken", accessToken,
"refreshToken", refreshToken,
"refreshToken", refreshTokenEntity.getToken(),
"isDefaultPassword", String.valueOf(user.isDefaultPassword())
));
}

public ResponseEntity<Map<String, String>> refreshToken(String refreshToken) {
if (!jwtUtils.validateToken(refreshToken)) {
throw ApiError.INVALID_CREDENTIALS.createException();
public ResponseEntity<Map<String, String>> refreshToken(String token) {
RefreshTokenEntity storedToken = refreshTokenRepository.findByToken(token).orElseThrow(() -> ApiError.INVALID_CREDENTIALS.createException("Refresh token not found"));

if (storedToken.isRevoked() || storedToken.getExpiryDate().before(new Date()) || !jwtUtils.validateToken(token)) {
throw ApiError.INVALID_CREDENTIALS.createException("Invalid or expired refresh token");
}

String username = jwtUtils.extractUsername(refreshToken);
BookLoreUserEntity user = userRepository.findByUsername(username).orElseThrow(() -> ApiError.USER_NOT_FOUND.createException(username));
BookLoreUserEntity user = storedToken.getUser();

storedToken.setRevoked(true);
storedToken.setRevocationDate(new Date());
refreshTokenRepository.save(storedToken);

String newAccessToken = jwtUtils.generateAccessToken(user);
String newRefreshToken = jwtUtils.generateRefreshToken(user);
RefreshTokenEntity newRefreshTokenEntity = RefreshTokenEntity.builder()
.user(user)
.token(newRefreshToken)
.expiryDate(new Date(System.currentTimeMillis() + jwtUtils.getRefreshTokenExpirationMs()))
.revoked(false)
.build();

refreshTokenRepository.save(newRefreshTokenEntity);

return ResponseEntity.ok(Map.of("accessToken", newAccessToken, "refreshToken", newRefreshToken));
return ResponseEntity.ok(Map.of(
"accessToken", jwtUtils.generateAccessToken(user),
"refreshToken", newRefreshToken
));
}
}
Original file line number Diff line number Diff line change
@@ -1,31 +1,33 @@
package com.adityachandel.booklore.config.security;

import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import com.adityachandel.booklore.model.entity.BookLoreUserEntity;
import com.adityachandel.booklore.service.JwtSecretService;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

import javax.crypto.SecretKey;
import java.util.Date;
import java.nio.charset.StandardCharsets;
import java.util.Date;

@Slf4j
@Service
@Component
@RequiredArgsConstructor
public class JwtUtils {

private final JwtSecretService jwtSecretService;
private final long accessTokenExpirationMs;
private final long refreshTokenExpirationMs;

public JwtUtils(JwtSecretService jwtSecretService) {
this.jwtSecretService = jwtSecretService;
this.accessTokenExpirationMs = 36000000; // 10 hours
this.refreshTokenExpirationMs = 604800000; // 7 days
}
@Getter
public final long accessTokenExpirationMs = 1000L * 60 * 60 * 10; // 10 hours
@Getter
public final long refreshTokenExpirationMs = 1000L * 60 * 60 * 24 * 30; // 30 days

private SecretKey getSigningKey() {
String secretKey = jwtSecretService.getSecret();
Expand Down Expand Up @@ -83,8 +85,4 @@ public Long extractUserId(String token) {
}
throw new IllegalArgumentException("Invalid userId claim type");
}

public boolean isTokenExpired(String token) {
return extractClaims(token).getExpiration().before(new Date());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.adityachandel.booklore.model.entity;

import jakarta.persistence.*;
import lombok.*;

import java.util.Date;

@Entity
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "refresh_token")
public class RefreshTokenEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false, unique = true, length = 512)
private String token;

@OneToOne
@JoinColumn(name = "user_id", referencedColumnName = "id")
private BookLoreUserEntity user;

@Column(name = "expiry_date", nullable = false)
private Date expiryDate;

@Column(nullable = false)
private boolean revoked = false;

@Column(name = "revocation_date")
private Date revocationDate;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.adityachandel.booklore.repository;

import com.adityachandel.booklore.model.entity.BookLoreUserEntity;
import com.adityachandel.booklore.model.entity.RefreshTokenEntity;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface RefreshTokenRepository extends JpaRepository<RefreshTokenEntity, Long> {
Optional<RefreshTokenEntity> findByToken(String token);
void deleteByUser(BookLoreUserEntity user);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
CREATE TABLE refresh_token
(
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL,
token VARCHAR(512) NOT NULL,
expiry_date TIMESTAMP NOT NULL,
revoked BOOLEAN NOT NULL DEFAULT FALSE,
revocation_date TIMESTAMP NULL,

CONSTRAINT uq_refresh_token UNIQUE (token),
CONSTRAINT fk_refresh_token_user FOREIGN KEY (user_id)
REFERENCES users (id)
ON DELETE CASCADE
);
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import {FormsModule} from '@angular/forms';
import {BookFilterComponent} from './book-filter/book-filter.component';
import {Tooltip} from 'primeng/tooltip';
import {Fluid} from 'primeng/fluid';
import {UserService} from '../../../user.service';
import {UserService} from '../../../settings/user-management/user.service';
import {LockUnlockMetadataDialogComponent} from './lock-unlock-metadata-dialog/lock-unlock-metadata-dialog.component';

export enum EntityType {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {MetadataRefreshType} from '../../../../metadata/model/request/metadata-r
import {MetadataRefreshRequest} from '../../../../metadata/model/request/metadata-refresh-request.model';
import {UrlHelperService} from '../../../../utilities/service/url-helper.service';
import {NgClass, NgIf} from '@angular/common';
import {UserService} from '../../../../user.service';
import {UserService} from '../../../../settings/user-management/user.service';
import {filter} from 'rxjs';
import {EmailService} from '../../../../settings/email/email.service';
import {TieredMenu} from 'primeng/tieredmenu';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {Book, BookSetting} from '../../../model/book.model';
import {BookService} from '../../../service/book.service';
import {forkJoin} from 'rxjs';
import {Select} from 'primeng/select';
import {UserService} from '../../../../user.service';
import {UserService} from '../../../../settings/user-management/user.service';
import {ProgressSpinner} from 'primeng/progressspinner';
import {MessageService} from 'primeng/api';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {NgxExtendedPdfViewerModule} from 'ngx-extended-pdf-viewer';
import {BookService} from '../../service/book.service';
import {forkJoin, Subscription} from 'rxjs';
import {BookSetting} from '../../model/book.model';
import {UserService} from '../../../user.service';
import {UserService} from '../../../settings/user-management/user.service';
import {NgIf} from '@angular/common';
import {ProgressSpinner} from 'primeng/progressspinner';
import {MessageService} from 'primeng/api';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {Message} from 'primeng/message';
import {NgIf} from '@angular/common';
import {Password} from 'primeng/password';
import {MessageService, PrimeTemplate} from 'primeng/api';
import {UserService} from '../../../user.service';
import {UserService} from '../../../settings/user-management/user.service';
import {AuthService} from '../../service/auth.service';

@Component({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {BookService} from '../../../book/service/book.service';
import {BookState} from '../../../book/model/state/book-state.model';
import {Book} from '../../../book/model/book.model';
import {Divider} from 'primeng/divider';
import {UserService} from '../../../user.service';
import {UserService} from '../../../settings/user-management/user.service';
import {ProgressSpinner} from 'primeng/progressspinner';

@Component({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {Ripple} from 'primeng/ripple';
import {Button} from 'primeng/button';
import {Menu} from 'primeng/menu';
import {MenuItem} from 'primeng/api';
import {UserService} from '../../../user.service';
import {UserService} from '../../../settings/user-management/user.service';

@Component({
selector: '[app-menuitem]',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import {ThemeConfiguratorComponent} from '../theme-configurator/theme-configurat
import {LiveNotificationBoxComponent} from '../../../core/component/live-notification-box/live-notification-box.component';
import {BookUploaderComponent} from '../../../utilities/component/book-uploader/book-uploader.component';
import {AuthService} from '../../../core/service/auth.service';
import {UserService} from '../../../user.service';
import {UserProfileDialogComponent} from '../../../user-profile-dialog/user-profile-dialog.component';
import {UserService} from '../../../settings/user-management/user.service';
import {UserProfileDialogComponent} from '../../../settings/global-preferences/user-profile-dialog/user-profile-dialog.component';

@Component({
selector: 'app-topbar',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<div class="rounded-xl overflow-hidden bg-card">
<div class="rounded-xl overflow-hidden">
<p-tabs [value]="tab" lazy="true">
<p-tablist>
<p-tab value="view">Book Details</p-tab>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {Component, inject, OnDestroy, OnInit} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
import {BookMetadataCenterService} from './book-metadata-center.service';
import {UserService} from '../../user.service';
import {UserService} from '../../settings/user-management/user.service';
import {Book, BookRecommendation} from '../../book/model/book.model';
import {Subscription} from 'rxjs';
import {distinctUntilChanged, filter, map, switchMap, take} from 'rxjs/operators';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@
</p-progressSpinner>

<div class="flex items-center gap-6">
<p-button
icon="pi pi-refresh"
(onClick)="regenerateCover(metadata.bookId)"
pTooltip="Regenerate cover"
tooltipPosition="top"
[outlined]="true"
[disabled]="metadata['coverLocked']">
</p-button>

<p-fileupload
pTooltip="Upload cover"
tooltipPosition="top"
Expand All @@ -39,14 +48,6 @@
(onBeforeUpload)="onBeforeSend()">
</p-fileupload>

<p-button
icon="pi pi-refresh"
(onClick)="regenerateCover(metadata.bookId)"
pTooltip="Regenerate cover"
tooltipPosition="top"
[disabled]="metadata['coverLocked']">
</p-button>

<p-button *ngIf="!metadata['coverLocked']" icon="pi pi-lock-open" [outlined]="true" (onClick)="toggleLock('thumbnailUrl')" severity="success"></p-button>
<p-button *ngIf="metadata['coverLocked']" icon="pi pi-lock" [outlined]="true" (onClick)="toggleLock('thumbnailUrl')" severity="warn"></p-button>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
</div>

<div>
<p-button label="Search" type="submit" [disabled]="!isSearchEnabled || loading"></p-button>
<p-button label="Search" type="submit" outlined="true" [disabled]="!isSearchEnabled || loading"></p-button>
</div>
</div>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {Tag} from 'primeng/tag';
import {Book, BookMetadata, BookRecommendation} from '../../../book/model/book.model';
import {Divider} from 'primeng/divider';
import {UrlHelperService} from '../../../utilities/service/url-helper.service';
import {UserService} from '../../../user.service';
import {UserService} from '../../../settings/user-management/user.service';
import {SplitButton} from 'primeng/splitbutton';
import {MenuItem, MessageService} from 'primeng/api';
import {BookSenderComponent} from '../../../book/components/book-sender/book-sender.component';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@
</div>
</div>
<div class="flex flex-row gap-6">
<p-button severity="warn" label="Reset" (onClick)="reset()"></p-button>
<p-button label="Submit" (onClick)="submit()"></p-button>
<p-button severity="warn" outlined="true" label="Reset" (onClick)="reset()"></p-button>
<p-button label="Save" outlined="true" (onClick)="submit()"></p-button>
</div>
</div>

Expand Down
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {RadioButton} from 'primeng/radiobutton';
import {Divider} from 'primeng/divider';
import {Button} from 'primeng/button';
import {Tooltip} from 'primeng/tooltip';
import {User, UserBookPreferences, UserService} from '../../user.service';
import {User, UserBookPreferences, UserService} from '../user-management/user.service';

@Component({
selector: 'app-book-preferences',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ <h2 class="text-xl flex items-center gap-2">
tooltipPosition="right"
style="cursor: pointer;"></i>
</h2>
<p-button outlined="true" label="Create New Provider" (onClick)="openCreateProviderDialog()"></p-button>
<p-button outlined="true" icon="pi pi-plus" label="Create New Provider" (onClick)="openCreateProviderDialog()"></p-button>
</div>

<p-table [value]="emailProviders">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ <h2 class="text-xl flex items-center gap-2">
tooltipPosition="right"
style="cursor: pointer;"></i>
</h2>
<p-button outlined="true" label="Add New Recipient" (onClick)="openAddRecipientDialog()"></p-button>
<p-button outlined="true" icon="pi pi-plus" label="Add New Recipient" (onClick)="openAddRecipientDialog()"></p-button>
</div>

<p-table [value]="recipientEmails">
Expand Down
Loading
0