Changed to Redis Session; Further implemented FE
This commit is contained in:
parent
2550140811
commit
77cfaf35ed
|
|
@ -26,7 +26,6 @@ repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
|
|
||||||
val jjwtVersion: String = "0.12.6";
|
|
||||||
val bcVersion: String = "1.78.1";
|
val bcVersion: String = "1.78.1";
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
@ -34,15 +33,15 @@ dependencies {
|
||||||
implementation("org.springframework.boot:spring-boot-starter-security")
|
implementation("org.springframework.boot:spring-boot-starter-security")
|
||||||
implementation("org.springframework.boot:spring-boot-starter-web")
|
implementation("org.springframework.boot:spring-boot-starter-web")
|
||||||
implementation("org.springframework.boot:spring-boot-starter-websocket")
|
implementation("org.springframework.boot:spring-boot-starter-websocket")
|
||||||
|
implementation("org.springframework.boot:spring-boot-docker-compose")
|
||||||
|
implementation("org.springframework.boot:spring-boot-starter-data-redis")
|
||||||
|
implementation("org.springframework.session:spring-session-data-redis")
|
||||||
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
|
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
|
||||||
implementation("org.jetbrains.kotlin:kotlin-reflect")
|
implementation("org.jetbrains.kotlin:kotlin-reflect")
|
||||||
implementation("io.jsonwebtoken:jjwt-api:$jjwtVersion")
|
|
||||||
implementation("org.bouncycastle:bcprov-jdk18on:$bcVersion")
|
implementation("org.bouncycastle:bcprov-jdk18on:$bcVersion")
|
||||||
compileOnly("org.projectlombok:lombok")
|
compileOnly("org.projectlombok:lombok")
|
||||||
developmentOnly("org.springframework.boot:spring-boot-devtools")
|
developmentOnly("org.springframework.boot:spring-boot-devtools")
|
||||||
runtimeOnly("org.postgresql:postgresql")
|
runtimeOnly("org.postgresql:postgresql")
|
||||||
runtimeOnly("io.jsonwebtoken:jjwt-impl:$jjwtVersion")
|
|
||||||
runtimeOnly("io.jsonwebtoken:jjwt-jackson:$jjwtVersion")
|
|
||||||
annotationProcessor("org.projectlombok:lombok")
|
annotationProcessor("org.projectlombok:lombok")
|
||||||
testImplementation("org.springframework.boot:spring-boot-starter-test")
|
testImplementation("org.springframework.boot:spring-boot-starter-test")
|
||||||
testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
|
testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
version: "3.1"
|
||||||
|
|
||||||
|
services:
|
||||||
|
redis:
|
||||||
|
image: "redis:alpine"
|
||||||
|
ports:
|
||||||
|
- "6379:6379"
|
||||||
|
|
@ -4,9 +4,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties
|
||||||
|
|
||||||
@ConfigurationProperties("application")
|
@ConfigurationProperties("application")
|
||||||
data class ApplicationProperties(
|
data class ApplicationProperties(
|
||||||
|
val test: Boolean,
|
||||||
val corsAllowedOrigins: List<String>,
|
val corsAllowedOrigins: List<String>,
|
||||||
val corsAllowedMethods: List<String>,
|
val corsAllowedMethods: List<String>,
|
||||||
val jwtCookieName: String,
|
|
||||||
val jwtExpirationMs: Long,
|
|
||||||
val jwtSecret: String,
|
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -2,21 +2,23 @@ package at.eisibaer.jbear2.endpoint
|
||||||
|
|
||||||
import at.eisibaer.jbear2.dto.auth.LoginDto
|
import at.eisibaer.jbear2.dto.auth.LoginDto
|
||||||
import at.eisibaer.jbear2.dto.auth.LoginResponseDto
|
import at.eisibaer.jbear2.dto.auth.LoginResponseDto
|
||||||
import at.eisibaer.jbear2.model.Board
|
|
||||||
import at.eisibaer.jbear2.model.User
|
import at.eisibaer.jbear2.model.User
|
||||||
import at.eisibaer.jbear2.repository.UserRepository
|
import at.eisibaer.jbear2.repository.UserRepository
|
||||||
import at.eisibaer.jbear2.security.jwt.JwtUtils
|
import at.eisibaer.jbear2.security.UserDetailsImpl
|
||||||
import at.eisibaer.jbear2.security.userdetail.UserDetailsImpl
|
import at.eisibaer.jbear2.util.Constants.STR_SESSION_USER_KEY
|
||||||
|
import jakarta.servlet.http.HttpSession
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import org.springframework.http.HttpHeaders
|
|
||||||
import org.springframework.http.ResponseCookie
|
|
||||||
import org.springframework.http.ResponseEntity
|
import org.springframework.http.ResponseEntity
|
||||||
import org.springframework.security.authentication.AuthenticationManager
|
import org.springframework.security.authentication.AuthenticationManager
|
||||||
|
import org.springframework.security.authentication.BadCredentialsException
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
|
||||||
|
import org.springframework.security.core.Authentication
|
||||||
|
import org.springframework.security.core.AuthenticationException
|
||||||
import org.springframework.security.core.context.SecurityContextHolder
|
import org.springframework.security.core.context.SecurityContextHolder
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder
|
import org.springframework.security.crypto.password.PasswordEncoder
|
||||||
import org.springframework.stereotype.Controller
|
import org.springframework.stereotype.Controller
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping
|
||||||
import org.springframework.web.bind.annotation.PostMapping
|
import org.springframework.web.bind.annotation.PostMapping
|
||||||
import org.springframework.web.bind.annotation.RequestBody
|
import org.springframework.web.bind.annotation.RequestBody
|
||||||
import org.springframework.web.bind.annotation.RequestMapping
|
import org.springframework.web.bind.annotation.RequestMapping
|
||||||
|
|
@ -27,34 +29,27 @@ class AuthEndpoint(
|
||||||
val authenticationManager: AuthenticationManager,
|
val authenticationManager: AuthenticationManager,
|
||||||
val userRepository: UserRepository,
|
val userRepository: UserRepository,
|
||||||
val encoder: PasswordEncoder,
|
val encoder: PasswordEncoder,
|
||||||
val jwtUtils: JwtUtils,
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val log: Logger = LoggerFactory.getLogger(AuthEndpoint::class.java);
|
private val log: Logger = LoggerFactory.getLogger(AuthEndpoint::class.java);
|
||||||
|
|
||||||
val strResponseSuccess: String = "Sending back success response";
|
val strResponseSuccess: String = "Sending back success response";
|
||||||
|
val strAlreadyLoggedIn: String = "User already logged in";
|
||||||
|
|
||||||
@PostMapping("/signup")
|
@PostMapping("/signup")
|
||||||
fun signupUser(@RequestBody loginDto: LoginDto): ResponseEntity<String>{
|
fun signupUser(@RequestBody loginDto: LoginDto, session: HttpSession): ResponseEntity<*>{
|
||||||
log.info("Endpoint singupUser called");
|
log.info("Endpoint signupUser called");
|
||||||
log.debug("signup Request with username: {}", loginDto.username);
|
log.debug("signup Request with username: {}", loginDto.username);
|
||||||
|
|
||||||
if( userRepository.existsByUsername(loginDto.username)){
|
if( userRepository.existsByUsername(loginDto.username)){
|
||||||
log.info("Username was already taken");
|
log.info("Username was already taken");
|
||||||
ResponseEntity.badRequest().body("Username already taken");
|
return ResponseEntity.badRequest().body("Username already taken");
|
||||||
}
|
}
|
||||||
|
|
||||||
val user = User(loginDto.username, encoder.encode( loginDto.password), ArrayList(), null, null );
|
val user = User(loginDto.username, encoder.encode( loginDto.password), ArrayList(), null, null );
|
||||||
|
|
||||||
userRepository.save(user);
|
userRepository.save(user);
|
||||||
|
|
||||||
log.info(strResponseSuccess);
|
|
||||||
return ResponseEntity.ok().body("User registered successfully");
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/login")
|
|
||||||
fun loginUser(@RequestBody loginDto: LoginDto): ResponseEntity<LoginResponseDto>{
|
|
||||||
log.info("Endpoint loginUser called");
|
|
||||||
log.debug("login Request with username: {}", loginDto.username);
|
|
||||||
val authentication = authenticationManager.authenticate(
|
val authentication = authenticationManager.authenticate(
|
||||||
UsernamePasswordAuthenticationToken(
|
UsernamePasswordAuthenticationToken(
|
||||||
loginDto.username,
|
loginDto.username,
|
||||||
|
|
@ -66,22 +61,77 @@ class AuthEndpoint(
|
||||||
|
|
||||||
val userDetails: UserDetailsImpl = authentication.principal as UserDetailsImpl;
|
val userDetails: UserDetailsImpl = authentication.principal as UserDetailsImpl;
|
||||||
|
|
||||||
val jwtCookie = jwtUtils.generateJwtCookie(userDetails);
|
session.setAttribute(STR_SESSION_USER_KEY, userDetails);
|
||||||
|
|
||||||
|
log.info(strResponseSuccess);
|
||||||
|
return ResponseEntity.ok().body(LoginResponseDto(userDetails.username, userDetails.getProfilePictureFilename()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/login")
|
||||||
|
fun loginUser(@RequestBody loginDto: LoginDto, session: HttpSession): ResponseEntity<*>{
|
||||||
|
log.info("Endpoint loginUser called");
|
||||||
|
log.debug("login Request with username: {}", loginDto.username);
|
||||||
|
|
||||||
|
if( session.getAttribute(STR_SESSION_USER_KEY) != null ){
|
||||||
|
log.info(strAlreadyLoggedIn);
|
||||||
|
return ResponseEntity.badRequest().body(strAlreadyLoggedIn);
|
||||||
|
}
|
||||||
|
|
||||||
|
val authentication: Authentication;
|
||||||
|
try{
|
||||||
|
authentication = authenticationManager.authenticate(
|
||||||
|
UsernamePasswordAuthenticationToken(
|
||||||
|
loginDto.username,
|
||||||
|
loginDto.password
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} catch (authenticationException: AuthenticationException ){
|
||||||
|
if( authenticationException is BadCredentialsException ){
|
||||||
|
log.debug("Login attempt with bad credentials for username: {}", loginDto.username);
|
||||||
|
return ResponseEntity.status(401).body(4011);
|
||||||
|
}
|
||||||
|
log.error("Error during authentication", authenticationException);
|
||||||
|
return ResponseEntity.status(401).body(4010);
|
||||||
|
}
|
||||||
|
|
||||||
|
SecurityContextHolder.getContext().authentication = authentication;
|
||||||
|
|
||||||
|
val userDetails: UserDetailsImpl = authentication.principal as UserDetailsImpl;
|
||||||
|
|
||||||
|
session.setAttribute(STR_SESSION_USER_KEY, userDetails);
|
||||||
|
|
||||||
log.info(strResponseSuccess);
|
log.info(strResponseSuccess);
|
||||||
return ResponseEntity.ok()
|
return ResponseEntity.ok()
|
||||||
.header( HttpHeaders.SET_COOKIE, jwtCookie.toString() )
|
|
||||||
.body( LoginResponseDto(userDetails.username, userDetails.getProfilePictureFilename()))
|
.body( LoginResponseDto(userDetails.username, userDetails.getProfilePictureFilename()))
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("signout")
|
@PostMapping("/logout")
|
||||||
fun logoutUser(): ResponseEntity<String>{
|
fun logoutUser(session: HttpSession): ResponseEntity<String>{
|
||||||
log.info("Endpoint logoutUser called");
|
log.info("Endpoint logoutUser called");
|
||||||
val cookie: ResponseCookie = jwtUtils.getCleanJwtCookie();
|
|
||||||
|
session.invalidate();
|
||||||
|
|
||||||
log.info(strResponseSuccess);
|
log.info(strResponseSuccess);
|
||||||
return ResponseEntity.ok()
|
return ResponseEntity.ok()
|
||||||
.header(HttpHeaders.SET_COOKIE, cookie.toString())
|
|
||||||
.body("Logged out");
|
.body("Logged out");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/status")
|
||||||
|
fun checkStatus(session: HttpSession): ResponseEntity<*>{
|
||||||
|
log.info("Endpoint checkStatus called");
|
||||||
|
|
||||||
|
return if( session.getAttribute(STR_SESSION_USER_KEY) != null ){
|
||||||
|
log.info(strAlreadyLoggedIn);
|
||||||
|
val sessionUser: UserDetailsImpl = session.getAttribute(STR_SESSION_USER_KEY) as UserDetailsImpl;
|
||||||
|
ResponseEntity
|
||||||
|
.ok()
|
||||||
|
.body( LoginResponseDto( sessionUser.username, sessionUser.getProfilePictureFilename() ) );
|
||||||
|
} else {
|
||||||
|
log.debug("No user logged in");
|
||||||
|
log.info(strResponseSuccess);
|
||||||
|
ResponseEntity
|
||||||
|
.status(401)
|
||||||
|
.body("No user logged in");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -20,4 +20,9 @@ class UserEndpoint {
|
||||||
log.info("test Endpoint!");
|
log.info("test Endpoint!");
|
||||||
return ResponseEntity.ok(param1);
|
return ResponseEntity.ok(param1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/boards")
|
||||||
|
fun getBoards(){
|
||||||
|
TODO();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,39 +1,33 @@
|
||||||
package at.eisibaer.jbear2.security.jwt
|
package at.eisibaer.jbear2.security
|
||||||
|
|
||||||
|
import at.eisibaer.jbear2.util.Constants.STR_SESSION_USER_KEY
|
||||||
import jakarta.servlet.FilterChain
|
import jakarta.servlet.FilterChain
|
||||||
import jakarta.servlet.http.HttpServletRequest
|
import jakarta.servlet.http.HttpServletRequest
|
||||||
import jakarta.servlet.http.HttpServletResponse
|
import jakarta.servlet.http.HttpServletResponse
|
||||||
|
import jakarta.servlet.http.HttpSession
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
|
||||||
import org.springframework.security.core.context.SecurityContextHolder
|
import org.springframework.security.core.context.SecurityContextHolder
|
||||||
import org.springframework.security.core.userdetails.UserDetails
|
|
||||||
import org.springframework.security.core.userdetails.UserDetailsService
|
|
||||||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource
|
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource
|
||||||
import org.springframework.stereotype.Component
|
import org.springframework.stereotype.Component
|
||||||
import org.springframework.web.filter.OncePerRequestFilter
|
import org.springframework.web.filter.OncePerRequestFilter
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
class AuthTokenFilter(
|
class AuthFilter: OncePerRequestFilter() {
|
||||||
private var jwtUtils: JwtUtils?,
|
|
||||||
private var userDetailService: UserDetailsService?,
|
|
||||||
): OncePerRequestFilter() {
|
|
||||||
|
|
||||||
val log: Logger = LoggerFactory.getLogger(AuthTokenFilter::class.java);
|
val log: Logger = LoggerFactory.getLogger(AuthFilter::class.java);
|
||||||
|
|
||||||
override fun doFilterInternal(
|
override fun doFilterInternal(
|
||||||
request: HttpServletRequest,
|
request: HttpServletRequest,
|
||||||
response: HttpServletResponse,
|
response: HttpServletResponse,
|
||||||
filterChain: FilterChain
|
filterChain: FilterChain
|
||||||
) {
|
) {
|
||||||
|
val session: HttpSession = request.session;
|
||||||
try{
|
try{
|
||||||
val jwt: String? = parseJwt(request);
|
val user: UserDetailsImpl? = session.getAttribute(STR_SESSION_USER_KEY) as UserDetailsImpl?;
|
||||||
if( jwt != null && jwtUtils!!.validateJwt(jwt) ){
|
if( user != null ){
|
||||||
val username: String = jwtUtils!!.getUserNameFromJwt(jwt);
|
val authentication = UsernamePasswordAuthenticationToken(user, null, emptyList());
|
||||||
|
|
||||||
val userDetails: UserDetails = userDetailService!!.loadUserByUsername( username );
|
|
||||||
|
|
||||||
val authentication = UsernamePasswordAuthenticationToken(userDetails, null, userDetails.authorities);
|
|
||||||
authentication.details = WebAuthenticationDetailsSource().buildDetails(request);
|
authentication.details = WebAuthenticationDetailsSource().buildDetails(request);
|
||||||
|
|
||||||
SecurityContextHolder.getContext().authentication = authentication;
|
SecurityContextHolder.getContext().authentication = authentication;
|
||||||
|
|
@ -44,8 +38,4 @@ class AuthTokenFilter(
|
||||||
filterChain.doFilter(request, response);
|
filterChain.doFilter(request, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseJwt(request: HttpServletRequest): String?{
|
|
||||||
return jwtUtils!!.getJwtFromCookies(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
package at.eisibaer.jbear2.security
|
package at.eisibaer.jbear2.security
|
||||||
|
|
||||||
import at.eisibaer.jbear2.security.jwt.AuthTokenFilter
|
import at.eisibaer.jbear2.config.ApplicationProperties
|
||||||
import jakarta.servlet.FilterChain
|
import jakarta.servlet.FilterChain
|
||||||
import jakarta.servlet.ServletException
|
import jakarta.servlet.ServletException
|
||||||
import jakarta.servlet.http.HttpServletRequest
|
import jakarta.servlet.http.HttpServletRequest
|
||||||
|
|
@ -14,8 +14,6 @@ import org.springframework.security.authentication.dao.DaoAuthenticationProvider
|
||||||
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration
|
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration
|
||||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity
|
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||||
import org.springframework.security.config.annotation.web.configurers.SessionManagementConfigurer
|
|
||||||
import org.springframework.security.config.http.SessionCreationPolicy
|
|
||||||
import org.springframework.security.core.userdetails.UserDetailsService
|
import org.springframework.security.core.userdetails.UserDetailsService
|
||||||
import org.springframework.security.crypto.argon2.Argon2PasswordEncoder
|
import org.springframework.security.crypto.argon2.Argon2PasswordEncoder
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder
|
import org.springframework.security.crypto.password.PasswordEncoder
|
||||||
|
|
@ -32,33 +30,38 @@ import java.util.function.Supplier
|
||||||
@EnableMethodSecurity
|
@EnableMethodSecurity
|
||||||
class SecurityConfiguration(
|
class SecurityConfiguration(
|
||||||
private val userDetailService: UserDetailsService,
|
private val userDetailService: UserDetailsService,
|
||||||
private val unauthorizedHandler: AuthTokenFilter,
|
private val unauthorizedHandler: AuthFilter,
|
||||||
|
private val applicationProperties: ApplicationProperties
|
||||||
) {
|
) {
|
||||||
|
|
||||||
final val log: Logger = LoggerFactory.getLogger(SecurityConfiguration::class.java);
|
final val log: Logger = LoggerFactory.getLogger(SecurityConfiguration::class.java);
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
fun securityFilterChain(httpSecurity: HttpSecurity): SecurityFilterChain {
|
fun securityFilterChain(httpSecurity: HttpSecurity): SecurityFilterChain {
|
||||||
return httpSecurity
|
return addCsrfConfig(httpSecurity)
|
||||||
.csrf { config ->
|
.authorizeHttpRequests { config ->
|
||||||
|
config
|
||||||
|
.requestMatchers("/api/auth/*").permitAll()
|
||||||
|
.requestMatchers("/api/user/**").authenticated()
|
||||||
|
.requestMatchers("/**").permitAll()
|
||||||
|
}
|
||||||
|
.authenticationProvider(authenticationProvider())
|
||||||
|
.addFilterBefore(unauthorizedHandler, UsernamePasswordAuthenticationFilter::class.java)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addCsrfConfig(httpSecurity: HttpSecurity): HttpSecurity{
|
||||||
|
if( applicationProperties.test ){
|
||||||
|
httpSecurity.csrf{ config -> config.disable()};
|
||||||
|
} else {
|
||||||
|
httpSecurity.csrf { config ->
|
||||||
config
|
config
|
||||||
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
|
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
|
||||||
.csrfTokenRequestHandler(SpaCsrfTokenRequestHandler())
|
.csrfTokenRequestHandler(SpaCsrfTokenRequestHandler())
|
||||||
}
|
}
|
||||||
.addFilterAfter(CsrfCookieFilter(), BasicAuthenticationFilter::class.java)
|
.addFilterAfter(CsrfCookieFilter(), BasicAuthenticationFilter::class.java)
|
||||||
.authorizeHttpRequests { config ->
|
|
||||||
config
|
|
||||||
.requestMatchers("/api/auth/*").permitAll()
|
|
||||||
.requestMatchers("/api/**").authenticated()
|
|
||||||
.requestMatchers("/profile").authenticated()
|
|
||||||
.requestMatchers("/**").permitAll()
|
|
||||||
}
|
}
|
||||||
.sessionManagement { config: SessionManagementConfigurer<HttpSecurity?> ->
|
return httpSecurity;
|
||||||
config.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
|
|
||||||
}
|
|
||||||
.authenticationProvider(authenticationProvider())
|
|
||||||
.addFilterBefore(unauthorizedHandler, UsernamePasswordAuthenticationFilter::class.java)
|
|
||||||
.build()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class SpaCsrfTokenRequestHandler : CsrfTokenRequestAttributeHandler() {
|
class SpaCsrfTokenRequestHandler : CsrfTokenRequestAttributeHandler() {
|
||||||
|
|
@ -94,7 +97,6 @@ class SecurityConfiguration(
|
||||||
}
|
}
|
||||||
|
|
||||||
class CsrfCookieFilter : OncePerRequestFilter() {
|
class CsrfCookieFilter : OncePerRequestFilter() {
|
||||||
|
|
||||||
@Throws(ServletException::class, IOException::class)
|
@Throws(ServletException::class, IOException::class)
|
||||||
override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, filterChain: FilterChain) {
|
override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, filterChain: FilterChain) {
|
||||||
val csrfToken = request.getAttribute("_csrf") as CsrfToken
|
val csrfToken = request.getAttribute("_csrf") as CsrfToken
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package at.eisibaer.jbear2.security.userdetail
|
package at.eisibaer.jbear2.security
|
||||||
|
|
||||||
import at.eisibaer.jbear2.repository.UserRepository
|
import at.eisibaer.jbear2.repository.UserRepository
|
||||||
import at.eisibaer.jbear2.model.User
|
import at.eisibaer.jbear2.model.User
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package at.eisibaer.jbear2.security.userdetail
|
package at.eisibaer.jbear2.security
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||||
import lombok.Data
|
import lombok.Data
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
package at.eisibaer.jbear2.security.jwt
|
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest
|
|
||||||
import jakarta.servlet.http.HttpServletResponse
|
|
||||||
import org.slf4j.Logger
|
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
import org.springframework.security.core.AuthenticationException
|
|
||||||
import org.springframework.security.web.AuthenticationEntryPoint
|
|
||||||
|
|
||||||
class AuthEntryPointJwt: AuthenticationEntryPoint {
|
|
||||||
|
|
||||||
val log: Logger = LoggerFactory.getLogger(AuthEntryPointJwt::class.java);
|
|
||||||
|
|
||||||
override fun commence(
|
|
||||||
request: HttpServletRequest?,
|
|
||||||
response: HttpServletResponse?,
|
|
||||||
authException: AuthenticationException?
|
|
||||||
) {
|
|
||||||
log.error("Unauthorized error: {}", authException?.message);
|
|
||||||
response?.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Error: Unauthorized");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,75 +0,0 @@
|
||||||
package at.eisibaer.jbear2.security.jwt
|
|
||||||
|
|
||||||
import at.eisibaer.jbear2.config.ApplicationProperties
|
|
||||||
import at.eisibaer.jbear2.security.userdetail.UserDetailsImpl
|
|
||||||
import io.jsonwebtoken.JwtException
|
|
||||||
import io.jsonwebtoken.Jwts
|
|
||||||
import io.jsonwebtoken.UnsupportedJwtException
|
|
||||||
import io.jsonwebtoken.io.Decoders
|
|
||||||
import io.jsonwebtoken.security.Keys
|
|
||||||
import jakarta.servlet.http.HttpServletRequest
|
|
||||||
import org.slf4j.Logger
|
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
import org.springframework.http.ResponseCookie
|
|
||||||
import org.springframework.stereotype.Component
|
|
||||||
import org.springframework.web.util.WebUtils
|
|
||||||
import java.util.Date
|
|
||||||
import javax.crypto.SecretKey
|
|
||||||
|
|
||||||
@Component
|
|
||||||
class JwtUtils(
|
|
||||||
private val applicationProperties: ApplicationProperties
|
|
||||||
) {
|
|
||||||
|
|
||||||
private val log: Logger = LoggerFactory.getLogger(JwtUtils::class.java);
|
|
||||||
|
|
||||||
fun getJwtFromCookies(request: HttpServletRequest): String?{
|
|
||||||
return WebUtils.getCookie(request, applicationProperties.jwtCookieName)?.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
fun generateJwtCookie(userPrincipal: UserDetailsImpl): ResponseCookie{
|
|
||||||
val jwt: String = generateTokenFromUsername(userPrincipal.username)
|
|
||||||
return ResponseCookie.from(applicationProperties.jwtCookieName, jwt)
|
|
||||||
.path("/api")
|
|
||||||
.maxAge(applicationProperties.jwtExpirationMs/1000)
|
|
||||||
.httpOnly(true)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getCleanJwtCookie(): ResponseCookie {
|
|
||||||
return ResponseCookie.from(applicationProperties.jwtCookieName, "")
|
|
||||||
.path("/api")
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getUserNameFromJwt(token: String): String{
|
|
||||||
return Jwts.parser().verifyWith(key()).build().parseSignedClaims(token).payload.subject;
|
|
||||||
}
|
|
||||||
|
|
||||||
fun key(): SecretKey{
|
|
||||||
return Keys.hmacShaKeyFor(Decoders.BASE64.decode(applicationProperties.jwtSecret));
|
|
||||||
}
|
|
||||||
|
|
||||||
fun generateTokenFromUsername(username: String): String{
|
|
||||||
return Jwts.builder()
|
|
||||||
.subject(username)
|
|
||||||
.issuedAt(Date())
|
|
||||||
.expiration(Date(Date().time + applicationProperties.jwtExpirationMs))
|
|
||||||
.signWith(key())
|
|
||||||
.compact();
|
|
||||||
}
|
|
||||||
|
|
||||||
fun validateJwt(authToken: String): Boolean{
|
|
||||||
try {
|
|
||||||
Jwts.parser().verifyWith(key()).build().parseSignedClaims(authToken);
|
|
||||||
return true;
|
|
||||||
} catch (e: UnsupportedJwtException) {
|
|
||||||
log.error("UnsupportedJwtException {}", e.message);
|
|
||||||
} catch (e: IllegalArgumentException) {
|
|
||||||
log.error("IllegalArgumentException {}", e.message);
|
|
||||||
} catch (e: JwtException) {
|
|
||||||
log.error("JwtException {}", e.message);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
package at.eisibaer.jbear2.util
|
||||||
|
|
||||||
|
object Constants {
|
||||||
|
const val STR_SESSION_USER_KEY = "user"
|
||||||
|
}
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
logging:
|
logging:
|
||||||
level:
|
level:
|
||||||
org:
|
at:
|
||||||
springframework:
|
eisibaer:
|
||||||
security: "DEBUG"
|
jbear2: "DEBUG"
|
||||||
spring:
|
spring:
|
||||||
datasource:
|
datasource:
|
||||||
url: jdbc:postgresql://localhost:5499/jeobeardy?currentSchema=jeobeardy-app
|
url: jdbc:postgresql://${PG_HOST}:${PG_PORT}/jeobeardy?currentSchema=jeobeardy-app
|
||||||
username: ${PG_USER}
|
username: ${PG_USER}
|
||||||
password: ${PG_PASSWORD}
|
password: ${PG_PASSWORD}
|
||||||
|
|
||||||
application:
|
application:
|
||||||
|
test: true
|
||||||
cors-allowed-origins: [ "http://localhost:5173/" ]
|
cors-allowed-origins: [ "http://localhost:5173/" ]
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
spring:
|
spring:
|
||||||
datasource:
|
datasource:
|
||||||
url: jdbc:postgresql://localhost:5499/jeobeardy?currentSchema=jeobeardy-app
|
url: jdbc:postgresql://${PG_HOST}:${PG_PORT}/jeobeardy?currentSchema=jeobeardy-app
|
||||||
username: ${PG_USER}
|
username: ${PG_USER}
|
||||||
password: ${PG_PASSWORD}
|
password: ${PG_PASSWORD}
|
||||||
|
|
||||||
application:
|
application:
|
||||||
|
test: false
|
||||||
cors-allowed-origins: []
|
cors-allowed-origins: []
|
||||||
|
|
@ -11,6 +11,9 @@ spring:
|
||||||
dialect: org.hibernate.dialect.PostgreSQLDialect
|
dialect: org.hibernate.dialect.PostgreSQLDialect
|
||||||
default-schema: jeobeardy-app
|
default-schema: jeobeardy-app
|
||||||
open-in-view: false
|
open-in-view: false
|
||||||
|
docker:
|
||||||
|
compose:
|
||||||
|
lifecycle-management: start-only
|
||||||
|
|
||||||
server:
|
server:
|
||||||
address: localhost
|
address: localhost
|
||||||
|
|
@ -18,6 +21,3 @@ server:
|
||||||
|
|
||||||
application:
|
application:
|
||||||
cors-allowed-methods: ["GET", "POST", "DELETE", "OPTIONS"]
|
cors-allowed-methods: ["GET", "POST", "DELETE", "OPTIONS"]
|
||||||
jwt-cookie-name: "user_jwt"
|
|
||||||
jwt-expiration-ms: 86400000 #1000 * 60 * 60 * 24 = 1 day
|
|
||||||
jwt-secret: ${JWT_SECRET}
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -5,7 +5,7 @@
|
||||||
<link rel="icon" href="/favicon.ico">
|
<link rel="icon" href="/favicon.ico">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Vite App</title>
|
<title>Vite App</title>
|
||||||
<script type="module" crossorigin src="/assets/index-CPLpx4lq.js"></script>
|
<script type="module" crossorigin src="/assets/index-VvMfePyX.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-21nzev1V.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-21nzev1V.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
|
||||||
|
|
@ -1,37 +1,43 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { provide, ref } from 'vue';
|
||||||
import { RouterView } from 'vue-router';
|
import { RouterView } from 'vue-router';
|
||||||
|
|
||||||
import NavBar from '@/components/blocks/NavBar.vue';
|
|
||||||
import FooterBlock from '@/components/blocks/FooterBlock.vue';
|
|
||||||
import GenericInfoModal from '@/components/modals/GenericInfoModal.vue';
|
|
||||||
import { provide, ref } from 'vue';
|
|
||||||
import { infoModalShowFnKey } from './services/UtilService';
|
import { infoModalShowFnKey } from './services/UtilService';
|
||||||
|
|
||||||
const infoModal = ref<InstanceType<typeof GenericInfoModal> | null>(null);
|
import NavBar from '@/components/blocks/NavBar.vue';
|
||||||
|
import GenericInfoModal from '@/components/modals/GenericInfoModal.vue';
|
||||||
|
import { useUserStore } from './stores/UserStore';
|
||||||
|
|
||||||
function showInfoModal(title: string, text: string): void{
|
const userStore = useUserStore();
|
||||||
if( infoModal.value ){
|
|
||||||
|
const userLoading = ref( true );
|
||||||
|
userStore.userCheckPromise
|
||||||
|
.finally( () => {
|
||||||
|
userLoading.value = false;
|
||||||
|
} );
|
||||||
|
|
||||||
|
const infoModal = ref<InstanceType<typeof GenericInfoModal> | null>( null );
|
||||||
|
|
||||||
|
function showInfoModal( title: string, text: string ): void {
|
||||||
|
if( infoModal.value ) {
|
||||||
infoModal.value.modalTitle = title;
|
infoModal.value.modalTitle = title;
|
||||||
infoModal.value.modalText = text;
|
infoModal.value.modalText = text;
|
||||||
infoModal.value.show();
|
infoModal.value.show();
|
||||||
} else {
|
} else {
|
||||||
console.error('Modal not yet available');
|
console.error( 'Modal not yet available' );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
provide(infoModalShowFnKey, showInfoModal);
|
provide( infoModalShowFnKey, showInfoModal );
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="vh-100 overflow-y-scroll overflow-x-hidden">
|
<div class="vh-100 overflow-y-scroll overflow-x-hidden">
|
||||||
<NavBar />
|
<NavBar :userLoading="userLoading" />
|
||||||
|
|
||||||
<RouterView />
|
<RouterView />
|
||||||
|
|
||||||
<!-- <FooterBlock /> -->
|
<GenericInfoModal ref="infoModal" />
|
||||||
<GenericInfoModal
|
|
||||||
ref="infoModal"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -1,3 +1,7 @@
|
||||||
.preserve-breaks {
|
.preserve-breaks {
|
||||||
white-space: preserve-breaks;
|
white-space: preserve-breaks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pointer {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
@ -27,16 +27,10 @@ $primary-accent-dark: $cyclamen;
|
||||||
|
|
||||||
$secondary-accent: $celestial-blue;
|
$secondary-accent: $celestial-blue;
|
||||||
$secondary-accent-dark: $celestial-blue;
|
$secondary-accent-dark: $celestial-blue;
|
||||||
// $secondary-accent: $jungle-green;
|
|
||||||
// $secondary-accent-dark: $jungle-green;
|
|
||||||
|
|
||||||
/* Bootstrap Colors overrides */
|
/* Bootstrap Colors overrides */
|
||||||
$primary: $primary-accent;
|
$primary: $primary-accent;
|
||||||
$secondary: $secondary-accent;
|
$secondary: $secondary-accent;
|
||||||
// $success: $green;
|
|
||||||
// $info: $cyan;
|
|
||||||
// $warning: $yellow;
|
|
||||||
// $danger: $red;
|
|
||||||
$light: $anti-flash-white;
|
$light: $anti-flash-white;
|
||||||
$light-accented: shade-color($anti-flash-white, 10%);
|
$light-accented: shade-color($anti-flash-white, 10%);
|
||||||
$dark: $space-cadet;
|
$dark: $space-cadet;
|
||||||
|
|
@ -49,18 +43,15 @@ $body-bg-dark: $space-cadet;
|
||||||
$body-secondary-bg-dark: $dark-accented;
|
$body-secondary-bg-dark: $dark-accented;
|
||||||
$body-bg: $anti-flash-white;
|
$body-bg: $anti-flash-white;
|
||||||
$body-secondary-bg: $light-accented;
|
$body-secondary-bg: $light-accented;
|
||||||
|
$dropdown-link-hover-bg: $dark-accented;
|
||||||
|
|
||||||
// $font-size-base: 1.5rem;
|
$modal-fade-transform: scale(.75);
|
||||||
|
|
||||||
// 3. Include remainder of required Bootstrap stylesheets (including any separate color mode stylesheets)
|
// 3. Include remainder of required Bootstrap stylesheets (including any separate color mode stylesheets)
|
||||||
@import "bootstrap/scss/variables";
|
@import "bootstrap/scss/variables";
|
||||||
@import "bootstrap/scss/variables-dark";
|
@import "bootstrap/scss/variables-dark";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// $navbar-dark-active-color: $primary;
|
|
||||||
// $navbar-light-active-color: $primary;
|
|
||||||
|
|
||||||
/* Bootstrap Color Map adjustments */
|
/* Bootstrap Color Map adjustments */
|
||||||
$custom-colors: (
|
$custom-colors: (
|
||||||
"gray": $gray-500,
|
"gray": $gray-500,
|
||||||
|
|
@ -73,18 +64,9 @@ $theme-colors: map-merge($theme-colors, $custom-colors);
|
||||||
|
|
||||||
// 5. Include remainder of required parts
|
// 5. Include remainder of required parts
|
||||||
@import "bootstrap/scss/bootstrap";
|
@import "bootstrap/scss/bootstrap";
|
||||||
// @import "bootstrap/scss/maps";
|
|
||||||
// @import "bootstrap/scss/mixins";
|
|
||||||
// @import "bootstrap/scss/root";
|
|
||||||
|
|
||||||
// 6. Optionally include any other parts as needed
|
// 6. Optionally include any other parts as needed
|
||||||
@import "bootstrap/scss/utilities";
|
@import "bootstrap/scss/utilities";
|
||||||
// @import "bootstrap/scss/reboot";
|
|
||||||
// @import "bootstrap/scss/type";
|
|
||||||
// @import "bootstrap/scss/images";
|
|
||||||
// @import "bootstrap/scss/containers";
|
|
||||||
// @import "bootstrap/scss/grid";
|
|
||||||
// @import "bootstrap/scss/helpers";
|
|
||||||
|
|
||||||
$utilities: map-merge(
|
$utilities: map-merge(
|
||||||
$utilities,
|
$utilities,
|
||||||
|
|
@ -98,7 +80,16 @@ $utilities: map-merge(
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
"height": map-merge(
|
||||||
|
map-get($utilities, "height"),
|
||||||
|
(
|
||||||
|
values: map-merge(
|
||||||
|
map-get(map-get($utilities, "height"), "values"),
|
||||||
|
(fit-content: fit-content),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -108,6 +99,4 @@ $utilities: map-merge(
|
||||||
// 8. Add additional custom code here
|
// 8. Add additional custom code here
|
||||||
@import "./exotic_theme.scss";
|
@import "./exotic_theme.scss";
|
||||||
|
|
||||||
@import "bootstrap/scss/bootstrap";
|
|
||||||
|
|
||||||
@import "../css/main.css";
|
@import "../css/main.css";
|
||||||
|
|
@ -30,11 +30,11 @@ const boards = ref([{
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="d-flex justify-content-around align-items-center">
|
<div class="d-flex justify-content-around align-items-center">
|
||||||
<button class="btn btn-sm btn-primary">
|
<button class="btn btn-sm btn-primary">
|
||||||
<font-awesome-icon :icon="['fas', 'edit']" />
|
<FontAwesomeIcon :icon="['fas', 'edit']" />
|
||||||
Edit
|
Edit
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-sm btn-primary">
|
<button class="btn btn-sm btn-primary">
|
||||||
<font-awesome-icon :icon="['fas', 'play']" />
|
<FontAwesomeIcon :icon="['fas', 'play']" />
|
||||||
Play
|
Play
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
|
||||||
import { onMounted, watch } from 'vue';
|
import { onMounted, watch } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
const localeLocalStorageKey = 'locale';
|
const localeLocalStorageKey = 'locale';
|
||||||
|
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
|
const { t } = i18n;
|
||||||
|
|
||||||
onMounted( () => {
|
onMounted( () => {
|
||||||
const initialLocale = localStorage.getItem( localeLocalStorageKey );
|
const initialLocale = localStorage.getItem( localeLocalStorageKey );
|
||||||
|
|
@ -23,14 +25,14 @@ watch(
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="dropdown">
|
<div class="dropdown">
|
||||||
<button class="btn btn-sm btn-outline-primary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
<button class="btn btn-sm btn-outline-primary dropdown-toggle" type="button" data-bs-toggle="dropdown"
|
||||||
{{ $t( `i18n.${$i18n.locale}.name` ) }}
|
aria-expanded="false">
|
||||||
|
<FontAwesomeIcon :icon="['fas', 'globe']" />
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
<li v-for=" locale in $i18n.availableLocales " :key="`locale-${locale}`" @click="$i18n.locale = locale"
|
<li v-for="locale in $i18n.availableLocales" :key="`locale-${locale}`">
|
||||||
role="button">
|
<a @click="$i18n.locale = locale" class="dropdown-item pointer" :class="[{ active: $i18n.locale === locale }]">
|
||||||
<a class="dropdown-item pointer" :class="[{ active: $i18n.locale === locale }]">
|
{{ t( `i18n.${locale}.name` ) }}
|
||||||
{{ $t( `i18n.${locale}.name` ) }}
|
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,60 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { RouterLink, useRoute } from 'vue-router';
|
import { ref } from 'vue';
|
||||||
|
import { RouterLink, useRoute, useRouter } from 'vue-router';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
import IconJeobeardy from '@/components/icons/IconJeobeardy.vue';
|
import IconJeobeardy from '@/components/icons/IconJeobeardy.vue';
|
||||||
import ThemeChanger from './ThemeChanger.vue';
|
import ThemeChanger from '@/components/blocks/ThemeChanger.vue';
|
||||||
import LocaleChanger from './LocaleChanger.vue';
|
import LocaleChanger from '@/components/blocks/LocaleChanger.vue';
|
||||||
import { useUserStore } from '@/stores/UserStore';
|
import { useUserStore } from '@/stores/UserStore';
|
||||||
|
import { authService } from '@/services/AuthService';
|
||||||
|
|
||||||
const navNames = {
|
const navNames = {
|
||||||
HOME: "home",
|
HOME: "home",
|
||||||
ABOUT: "about",
|
ABOUT: "about",
|
||||||
}
|
};
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
|
||||||
const isActiveNav = ( navName: string ) => {
|
const isActiveNav = ( navName: string ) => {
|
||||||
switch( navName ){
|
switch( navName ) {
|
||||||
default:
|
|
||||||
case navNames.HOME:
|
case navNames.HOME:
|
||||||
return route.name === "home";
|
return route.name === "home";
|
||||||
case navNames.ABOUT:
|
case navNames.ABOUT:
|
||||||
return route.name === "about";
|
return route.name === "about";
|
||||||
|
default:
|
||||||
|
return route.name === "home";
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function logoutUser() {
|
||||||
|
authService.logoutUser()
|
||||||
|
.then( () => {
|
||||||
|
userStore.logoutUser();
|
||||||
|
if( route.meta.requiresAuth ) {
|
||||||
|
router.push( { name: 'home' } );
|
||||||
|
}
|
||||||
|
} )
|
||||||
|
.catch( ( error ) => {
|
||||||
|
console.error( error );
|
||||||
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const userCheckLoading = ref( true );
|
||||||
|
userStore.userCheckPromise
|
||||||
|
.finally( () => {
|
||||||
|
userCheckLoading.value = false;
|
||||||
|
} )
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<nav class="navbar navbar-expand-lg bg-dark-accented">
|
<nav id="navbar-main" class="navbar navbar-expand-lg bg-dark-accented">
|
||||||
<div class="container px-5">
|
<div class="container px-5">
|
||||||
|
|
||||||
<div class="position-absolute start-0 top-50 translate-middle-y d-flex ms-3 gap-3">
|
<div class="position-absolute start-0 top-50 translate-middle-y d-flex ms-3 gap-3">
|
||||||
|
|
@ -45,31 +72,59 @@ const isActiveNav = ( navName: string ) => {
|
||||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||||
<ul class="navbar-nav mb-2 mb-lg-0 d-flex align-items-center justify-content-center w-100">
|
<ul class="navbar-nav mb-2 mb-lg-0 d-flex align-items-center justify-content-center w-100">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<RouterLink to="/" class="nav-link text-light fs-3" :class="[{active: isActiveNav(navNames.HOME)}]" :aria-current="isActiveNav(navNames.HOME) ? 'page' : false">{{ $t( 'nav.home' ) }}</RouterLink>
|
<RouterLink to="/" class="nav-link text-light fs-3"
|
||||||
|
:aria-current="isActiveNav( navNames.HOME ) ? 'page' : false">{{ t( 'nav.home' ) }}</RouterLink>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item px-5 mx-5 rounded-5 py-2">
|
<li class="nav-item px-5 mx-5 rounded-5 py-2">
|
||||||
<RouterLink to="/" class="nav-link py-0">
|
<RouterLink to="/" class="nav-link py-0">
|
||||||
<IconJeobeardy height="3rem" width="4rem"/>
|
<IconJeobeardy height="3rem" width="4rem" />
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<RouterLink to="/about" class="nav-link text-light fs-3" :class="[{active: isActiveNav(navNames.ABOUT)}]" :aria-current="isActiveNav(navNames.HOME) ? 'page' : false">{{ $t( 'nav.about' ) }}</RouterLink>
|
<RouterLink to="/about" class="nav-link text-light fs-3"
|
||||||
|
:aria-current="isActiveNav( navNames.HOME ) ? 'page' : false">{{ t( 'nav.about' ) }}</RouterLink>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="position-absolute end-0 top-50 translate-middle-y d-flex me-3">
|
<div class="position-absolute end-0 top-50 translate-middle-y d-flex me-3 align-items-center">
|
||||||
<div v-if="userStore.loggedIn">
|
<template v-if=" userCheckLoading ">
|
||||||
{{ userStore.getUserOutput }}
|
|
||||||
|
</template>
|
||||||
|
<template v-else-if=" userStore.loggedIn ">
|
||||||
|
<div class="dropdown-toggle pointer" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
|
<img class="pfp-sizing rounded-circle border border-1 border-primary" :src="userStore.pfpSource"
|
||||||
|
alt="The Profile Pic of the user" />
|
||||||
</div>
|
</div>
|
||||||
|
<ul class="dropdown-menu dropdown-menu-end">
|
||||||
|
<li>
|
||||||
|
<p class="dropdown-header fs-5 pt-0 text-primary fw-semibold">{{ userStore.getUserOutput }}</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<RouterLink class="dropdown-item" to="/profile" :class="[{ 'active': route.name === 'profile' }]">Profile
|
||||||
|
</RouterLink>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<hr class="dropdown-divider">
|
||||||
|
</li>
|
||||||
|
<li><span class="dropdown-item pointer text-danger" @click="logoutUser">Logout</span></li>
|
||||||
|
</ul>
|
||||||
|
</template>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<RouterLink to="/login" class="btn btn-sm btn-outline-primary">
|
<RouterLink to="/login" class="btn btn-sm btn-outline-primary">
|
||||||
Login
|
Login
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style lang="css" scoped>
|
||||||
|
.pfp-sizing {
|
||||||
|
width: 2.5rem;
|
||||||
|
height: 2.5rem;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -1,22 +1,27 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useBootstrapTheme } from '@/composables/colorTheme';
|
import { useBootstrapTheme } from '@/composables/colorTheme';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
const { availableThemes, currentTheme } = useBootstrapTheme();
|
const { availableThemes, currentTheme } = useBootstrapTheme();
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="theme-changer">
|
<div class="theme-changer">
|
||||||
|
|
||||||
<div class="dropdown">
|
<div class="dropdown">
|
||||||
<button class="btn btn-sm btn-outline-primary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
<button class="btn btn-sm btn-outline-primary dropdown-toggle" type="button" data-bs-toggle="dropdown"
|
||||||
|
aria-expanded="false">
|
||||||
<FontAwesomeIcon :icon="currentTheme.icon" />
|
<FontAwesomeIcon :icon="currentTheme.icon" />
|
||||||
{{ $t( currentTheme.name ) }}
|
<!-- {{ t( currentTheme.name ) }} -->
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
<li v-for="theme in availableThemes" :key="`theme-${theme.bsName}`" @click="currentTheme = theme" role="button">
|
<li v-for="theme in availableThemes" :key="`theme-${theme.bsName}`">
|
||||||
<a class="dropdown-item pointer" :class="[{active: theme.bsName === currentTheme.bsName}]">
|
<a class="dropdown-item pointer" :class="[{ active: theme.bsName === currentTheme.bsName }]"
|
||||||
|
@click="currentTheme = theme">
|
||||||
<FontAwesomeIcon :icon="theme.icon" />
|
<FontAwesomeIcon :icon="theme.icon" />
|
||||||
{{ $t(theme.name) }}
|
{{ t( theme.name ) }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Modal } from 'bootstrap';
|
||||||
|
import { onMounted, onUnmounted, ref } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modalId: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const modalRef = ref<null | Element>(null);
|
||||||
|
let modalInstance: null | Modal;
|
||||||
|
|
||||||
|
onMounted( () => {
|
||||||
|
modalInstance = Modal.getOrCreateInstance(modalRef.value as Element);
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted( () => {
|
||||||
|
modalInstance?.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
function show(){
|
||||||
|
if( modalInstance ){
|
||||||
|
modalInstance.show();
|
||||||
|
} else {
|
||||||
|
console.error("Modal was not properly created before showing");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hide(){
|
||||||
|
if( modalInstance ){
|
||||||
|
modalInstance.hide();
|
||||||
|
} else {
|
||||||
|
console.error("Modal was not properly created before hiding");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
show,
|
||||||
|
hide,
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="modal fade" tabindex="-1" ref="modalRef" :id="props.modalId">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">{{ t('profile.edit.title') }}</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>TODO</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-outline-primary" data-bs-dismiss="modal">{{ t("common.buttons.close") }}</button>
|
||||||
|
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">{{ t("common.buttons.saveAndExit") }}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -1,8 +1,12 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted } from 'vue';
|
import { ref, onMounted } from 'vue';
|
||||||
import { userService } from '@/services/UserService';
|
import { useI18n } from 'vue-i18n';
|
||||||
import IconJeobeardy from '../icons/IconJeobeardy.vue';
|
|
||||||
|
|
||||||
|
import { userService } from '@/services/UserService';
|
||||||
|
|
||||||
|
import IconJeobeardy from '@/components/icons/IconJeobeardy.vue';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
const testResponse = ref( {} );
|
const testResponse = ref( {} );
|
||||||
|
|
||||||
|
|
@ -20,7 +24,7 @@ onMounted( () => {
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="w-100 d-flex justify-content-center my-5">
|
<div class="w-100 d-flex justify-content-center my-5">
|
||||||
<h1>
|
<h1>
|
||||||
{{ $t( 'home.welcome' ) }}
|
{{ t( 'home.welcome' ) }}
|
||||||
{{ testResponse }}
|
{{ testResponse }}
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -32,51 +36,51 @@ onMounted( () => {
|
||||||
<div class="col-md-6 col-12 px-0 bg-body-secondary">
|
<div class="col-md-6 col-12 px-0 bg-body-secondary">
|
||||||
<div class="d-flex justify-content-center align-items-center h-100 w-100 flex-column">
|
<div class="d-flex justify-content-center align-items-center h-100 w-100 flex-column">
|
||||||
<h3 class="m-1">
|
<h3 class="m-1">
|
||||||
{{ $t("join.text") }}
|
{{ t("join.text") }}
|
||||||
</h3>
|
</h3>
|
||||||
<p>
|
<p>
|
||||||
{{ $t("join.alreadyHostedGome") }}
|
{{ t("join.alreadyHostedGome") }}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
{{ $t("join.textCode") }}
|
{{ t("join.textCode") }}
|
||||||
</p>
|
</p>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<input type="text" class="form-control" placeholder="Code">
|
<input type="text" class="form-control" placeholder="Code">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<button class="btn btn-primary">{{ $t("join.button") }}</button>
|
<button class="btn btn-primary">{{ t("join.button") }}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 col-12 px-0 mx-0">
|
<div class="col-md-6 col-12 px-0 mx-0">
|
||||||
<img class="w-100" src="/src/assets/images/OldInGameBlurredRotated.jpeg"
|
<img class="w-100" src="/src/assets/images/OldInGameBlurredRotated.jpeg"
|
||||||
alt="Blurred, slightly tilted image of how the game looks like">
|
alt="Blurred, slightly tilted view of how a board looks like">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row w-100 border-bottom">
|
<div class="row w-100 border-bottom">
|
||||||
<div class="col-md-6 col-12 px-0 mx-0">
|
<div class="col-md-6 col-12 px-0 mx-0">
|
||||||
<img class="w-100" src="/src/assets/images/OldInGameBlurredRotated.jpeg"
|
<img class="w-100" src="/src/assets/images/OldInGameBlurredRotated.jpeg"
|
||||||
alt="Blurred, slightly tilted image of how the game looks like">
|
alt="Blurred, slightly tilted view of how the a board looks like">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 col-12 px-0 mx-0 bg-body-secondary">
|
<div class="col-md-6 col-12 px-0 mx-0 bg-body-secondary">
|
||||||
<div class="h-100 w-100 d-flex justify-content-center align-items-center flex-column">
|
<div class="h-100 w-100 d-flex justify-content-center align-items-center flex-column">
|
||||||
<h3 class="m-1">
|
<h3 class="m-1">
|
||||||
{{ $t("host.text") }}
|
{{ t("host.text") }}
|
||||||
</h3>
|
</h3>
|
||||||
<p>
|
<p>
|
||||||
{{ $t("host.alreadyHostedGome") }}
|
{{ t("host.alreadyHostedGome") }}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
{{ $t("host.textCode") }}
|
{{ t("host.textCode") }}
|
||||||
</p>
|
</p>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<input type="text" class="form-control" placeholder="Code">
|
<input type="text" class="form-control" placeholder="Code">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<button class="btn btn-primary">{{ $t("host.button") }}</button>
|
<button class="btn btn-primary">{{ t("host.button") }}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,35 +1,33 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { LoginDto } from '@/models/dto/LoginDto';
|
import type { LoginDto } from '@/models/dto/LoginDto';
|
||||||
import type { User } from '@/models/user/User';
|
|
||||||
import { authService } from '@/services/AuthService';
|
import { authService } from '@/services/AuthService';
|
||||||
import { infoModalShowFnKey } from '@/services/UtilService';
|
import { infoModalShowFnKey } from '@/services/UtilService';
|
||||||
import { useUserStore } from '@/stores/UserStore';
|
import { useUserStore } from '@/stores/UserStore';
|
||||||
import useVuelidate from '@vuelidate/core';
|
import useVuelidate from '@vuelidate/core';
|
||||||
import { createI18nMessage, required } from '@vuelidate/validators';
|
import { createI18nMessage, required } from '@vuelidate/validators';
|
||||||
|
import { AxiosError } from 'axios';
|
||||||
import { computed, inject, ref } from 'vue';
|
import { computed, inject, ref } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { RouterLink, useRouter } from 'vue-router';
|
import { RouterLink, useRoute, useRouter } from 'vue-router';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
|
||||||
const showInfoModal = inject(infoModalShowFnKey);
|
const showInfoModal = inject( infoModalShowFnKey );
|
||||||
|
|
||||||
const username = ref('');
|
const username = ref( '' );
|
||||||
const password = ref('');
|
const password = ref( '' );
|
||||||
|
|
||||||
const errorMessage = ref('');
|
const errorMessage = ref( '' );
|
||||||
|
|
||||||
const loginInProgress = ref(false);
|
const loginInProgress = ref( false );
|
||||||
function loginUser(){
|
function loginUser() {
|
||||||
v$.value.$touch();
|
v$.value.$touch();
|
||||||
if(v$.value.$error){
|
if( v$.value.$error ) {
|
||||||
errorMessage.value = t('forms.validate-fields');
|
showErrorMessage(t( 'forms.validate-fields' ));
|
||||||
setTimeout(() => {
|
|
||||||
errorMessage.value = '';
|
|
||||||
}, 3000);
|
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
errorMessage.value = '';
|
errorMessage.value = '';
|
||||||
|
|
@ -38,31 +36,46 @@ function loginUser(){
|
||||||
loginInProgress.value = true;
|
loginInProgress.value = true;
|
||||||
authService.loginUser( loginDto )
|
authService.loginUser( loginDto )
|
||||||
.then( ( response ) => {
|
.then( ( response ) => {
|
||||||
userStore.setUser(response as User);
|
userStore.loginUser( response );
|
||||||
router.push({ name: 'profile'});
|
if( route.query.r ){
|
||||||
})
|
router.push( { name: route.query.r.toString() } );
|
||||||
.catch( ( err ) => {
|
|
||||||
console.error(err);
|
|
||||||
const modalText = t('login.error.process');
|
|
||||||
if( showInfoModal !== undefined ){
|
|
||||||
showInfoModal(t('common.error.generic'), modalText);
|
|
||||||
} else {
|
} else {
|
||||||
alert(modalText);
|
router.push( { name: 'profile' } );
|
||||||
}
|
}
|
||||||
})
|
} )
|
||||||
|
.catch( ( err: Error | AxiosError ) => {
|
||||||
|
console.error( err );
|
||||||
|
if( err instanceof AxiosError && err.response?.data === 4011 ){
|
||||||
|
showErrorMessage(t('login.error.credentials'));
|
||||||
|
} else {
|
||||||
|
const modalText = t( 'login.error.process' );
|
||||||
|
if( showInfoModal !== undefined ) {
|
||||||
|
showInfoModal( t( 'common.error.generic' ), modalText );
|
||||||
|
} else {
|
||||||
|
alert( modalText );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} )
|
||||||
.finally( () => {
|
.finally( () => {
|
||||||
loginInProgress.value = false;
|
loginInProgress.value = false;
|
||||||
})
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
const withI18nMessage = createI18nMessage({t: t});
|
function showErrorMessage(messageText: string, msTimeShown: number = 3000): void {
|
||||||
const inputRequired = withI18nMessage(required);
|
errorMessage.value = messageText;
|
||||||
const rules = computed( () => ({
|
setTimeout( () => {
|
||||||
|
errorMessage.value = '';
|
||||||
|
}, msTimeShown );
|
||||||
|
}
|
||||||
|
|
||||||
|
const withI18nMessage = createI18nMessage( { t: t } );
|
||||||
|
const inputRequired = withI18nMessage( required );
|
||||||
|
const rules = computed( () => ( {
|
||||||
username: { inputRequired },
|
username: { inputRequired },
|
||||||
password: { inputRequired },
|
password: { inputRequired },
|
||||||
}));
|
} ) );
|
||||||
|
|
||||||
const v$ = useVuelidate(rules, { username, password });
|
const v$ = useVuelidate( rules, { username, password } );
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -81,19 +94,21 @@ const v$ = useVuelidate(rules, { username, password });
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="input-username">
|
<label for="input-username">
|
||||||
{{ t( 'login.username' ) }}
|
{{ t( 'login.username' ) }}
|
||||||
<span v-if="v$.username.$error" class="text-danger ps-3">
|
<span v-if=" v$.username.$error " class="text-danger ps-3">
|
||||||
{{ v$.username.$errors[0].$message }}
|
{{ v$.username.$errors[0].$message }}
|
||||||
</span></label>
|
</span></label>
|
||||||
<input v-model="username" type="text" id="input-username" class="form-control" :class="[{'border-danger': v$.username.$error}]" @blur="v$.username.$touch">
|
<input v-model="username" type="text" id="input-username" class="form-control"
|
||||||
|
:class="[{ 'border-danger': v$.username.$error }]" @blur="v$.username.$touch" @keyup.enter="loginUser">
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="input-username">
|
<label for="input-username">
|
||||||
{{ t( 'login.password' ) }}
|
{{ t( 'login.password' ) }}
|
||||||
<span v-if="v$.password.$error" class="text-danger ps-3">
|
<span v-if=" v$.password.$error " class="text-danger ps-3">
|
||||||
{{ v$.password.$errors[0].$message }}
|
{{ v$.password.$errors[0].$message }}
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<input v-model="password" type="password" id="input-username" class="form-control" :class="[{'border-danger': v$.password.$error}]" @blur="v$.password.$touch">
|
<input v-model="password" type="password" id="input-username" class="form-control"
|
||||||
|
:class="[{ 'border-danger': v$.password.$error }]" @blur="v$.password.$touch" @keyup.enter="loginUser">
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3 d-flex justify-content-between">
|
<div class="mb-3 d-flex justify-content-between">
|
||||||
<RouterLink to="/signup" class="btn btn-outline-primary">
|
<RouterLink to="/signup" class="btn btn-outline-primary">
|
||||||
|
|
@ -103,7 +118,7 @@ const v$ = useVuelidate(rules, { username, password });
|
||||||
{{ t( "login.loginButton" ) }}
|
{{ t( "login.loginButton" ) }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="errorMessage" class="alert alert-danger" role="alert">{{ errorMessage }}</div>
|
<div v-if=" errorMessage " class="alert alert-danger" role="alert">{{ errorMessage }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,43 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useUserStore } from '@/stores/UserStore';
|
|
||||||
import { computed } from 'vue';
|
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
import { useUserStore } from '@/stores/UserStore';
|
||||||
|
|
||||||
import BoardSelector from '@/components/blocks/BoardSelector.vue';
|
import BoardSelector from '@/components/blocks/BoardSelector.vue';
|
||||||
|
import EditProfileModal from '@/components/modals/EditProfileModal.vue';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
|
||||||
const pfpSource = computed( () => {
|
const editProfileModalId = "edit-profile-modal-on-profile-page";
|
||||||
return ( userStore.profilePicture === null ? "/src/assets/images/PFP_BearHead.svg" : userStore.profilePicture )
|
|
||||||
})
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="d-flex flex-column justify-content-center align-items-center mt-5">
|
<div class="d-flex flex-column justify-content-center align-items-center mt-5">
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col d-flex justify-content-center align-items-center flex-column">
|
||||||
<h1>
|
<h1>
|
||||||
{{ $t('profile.yourProfile') }}
|
{{ t('profile.yourProfile') }}
|
||||||
</h1>
|
</h1>
|
||||||
<div class="ratio ratio-1x1 border rounded-5" style="width: 15rem;">
|
<div class="ratio ratio-1x1 border rounded-5" style="width: 15rem;">
|
||||||
<img :src="pfpSource" alt="Your Profile Picture" />
|
<img :src="userStore.pfpSource" alt="Your Profile Pic" />
|
||||||
</div>
|
</div>
|
||||||
<p class="fs-3">
|
<p class="fs-3">
|
||||||
{{ userStore.username }}
|
{{ userStore.username }}
|
||||||
</p>
|
</p>
|
||||||
|
<div class="d-flex gap-3">
|
||||||
|
<button class="btn btn-outline-primary" :data-bs-target="`#${editProfileModalId}`" data-bs-toggle="modal">
|
||||||
|
Edit Profile
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row bg-body-secondary w-100 py-4">
|
<div class="row bg-body-secondary w-100 py-4">
|
||||||
<div class="col text-center">
|
<div class="col text-center">
|
||||||
<h2>
|
<h2>
|
||||||
{{ $t('profile.yourBoards') }}
|
{{ t('profile.yourBoards') }}
|
||||||
</h2>
|
</h2>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<BoardSelector />
|
<BoardSelector />
|
||||||
|
|
@ -38,10 +47,11 @@ const pfpSource = computed( () => {
|
||||||
<div class="row w-100 py-4 mb-5">
|
<div class="row w-100 py-4 mb-5">
|
||||||
<div class="col text-center">
|
<div class="col text-center">
|
||||||
<h2>
|
<h2>
|
||||||
{{ $t('settings.heading') }}
|
{{ t('settings.heading') }}
|
||||||
</h2>
|
</h2>
|
||||||
<button class="btn btn-outline-primary">{{ $t('profile.gotoSettings') }}</button>
|
<button class="btn btn-outline-primary">{{ t('profile.gotoSettings') }}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<EditProfileModal :modalId="editProfileModalId"/>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { type LoginDto } from '@/models/dto/LoginDto';
|
import { type LoginDto } from '@/models/dto/LoginDto';
|
||||||
import type { User } from '@/models/user/User';
|
|
||||||
import { authService } from '@/services/AuthService';
|
import { authService } from '@/services/AuthService';
|
||||||
import { infoModalShowFnKey } from '@/services/UtilService';
|
import { infoModalShowFnKey } from '@/services/UtilService';
|
||||||
import { useUserStore } from '@/stores/UserStore';
|
import { useUserStore } from '@/stores/UserStore';
|
||||||
|
|
@ -15,22 +14,22 @@ const router = useRouter();
|
||||||
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
|
||||||
const showInfoModal = inject(infoModalShowFnKey);
|
const showInfoModal = inject( infoModalShowFnKey );
|
||||||
|
|
||||||
const username = ref('');
|
const username = ref( '' );
|
||||||
const password = ref('');
|
const password = ref( '' );
|
||||||
const passwordRepeat = ref('');
|
const passwordRepeat = ref( '' );
|
||||||
|
|
||||||
const errorMessage = ref('');
|
const errorMessage = ref( '' );
|
||||||
|
|
||||||
const signupInProgress = ref(false);
|
const signupInProgress = ref( false );
|
||||||
function signupUser(){
|
function signupUser() {
|
||||||
v$.value.$touch();
|
v$.value.$touch();
|
||||||
if(v$.value.$error){
|
if( v$.value.$error ) {
|
||||||
errorMessage.value = t('forms.validate-fields');
|
errorMessage.value = t( 'forms.validate-fields' );
|
||||||
setTimeout(() => {
|
setTimeout( () => {
|
||||||
errorMessage.value = '';
|
errorMessage.value = '';
|
||||||
}, 3000);
|
}, 3000 );
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
errorMessage.value = '';
|
errorMessage.value = '';
|
||||||
|
|
@ -39,32 +38,32 @@ function signupUser(){
|
||||||
signupInProgress.value = true;
|
signupInProgress.value = true;
|
||||||
authService.signupUser( signupDto )
|
authService.signupUser( signupDto )
|
||||||
.then( ( response ) => {
|
.then( ( response ) => {
|
||||||
userStore.setUser(response as User);
|
userStore.loginUser( response );
|
||||||
router.push({ name: 'profile'});
|
router.push( { name: 'profile' } );
|
||||||
})
|
} )
|
||||||
.catch( ( err ) => {
|
.catch( ( err ) => {
|
||||||
console.error(err);
|
console.error( err );
|
||||||
const modalText = t('signup.error.process');
|
const modalText = t( 'login.error.process' );
|
||||||
if( showInfoModal !== undefined ){
|
if( showInfoModal !== undefined ) {
|
||||||
showInfoModal(t('common.error.generic'), modalText);
|
showInfoModal( t( 'common.error.generic' ), modalText );
|
||||||
} else {
|
} else {
|
||||||
alert(modalText);
|
alert( modalText );
|
||||||
}
|
}
|
||||||
})
|
} )
|
||||||
.finally( () => {
|
.finally( () => {
|
||||||
signupInProgress.value = false;
|
signupInProgress.value = false;
|
||||||
})
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
const withI18nMessage = createI18nMessage({t: t});
|
const withI18nMessage = createI18nMessage( { t: t } );
|
||||||
const inputRequired = withI18nMessage(required);
|
const inputRequired = withI18nMessage( required );
|
||||||
const rules = computed( () => ({
|
const rules = computed( () => ( {
|
||||||
username: { inputRequired },
|
username: { inputRequired },
|
||||||
password: { inputRequired, minLength: withI18nMessage(minLength(10)) },
|
password: { inputRequired, minLength: withI18nMessage( minLength( 10 ) ) },
|
||||||
passwordRepeat: { inputRequired, sameAs: withI18nMessage(sameAs(password.value, t('login.password'))) },
|
passwordRepeat: { inputRequired, sameAs: withI18nMessage( sameAs( password.value, t( 'login.password' ) ) ) },
|
||||||
}));
|
} ) );
|
||||||
|
|
||||||
const v$ = useVuelidate(rules, { username, password, passwordRepeat });
|
const v$ = useVuelidate( rules, { username, password, passwordRepeat } );
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -83,40 +82,44 @@ const v$ = useVuelidate(rules, { username, password, passwordRepeat });
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="input-username">
|
<label for="input-username">
|
||||||
{{ t( 'login.username' ) }}
|
{{ t( 'login.username' ) }}
|
||||||
<span v-if="v$.username.$error" class="text-danger ps-3">
|
<span v-if=" v$.username.$error " class="text-danger ps-3">
|
||||||
{{ v$.username.$errors[0].$message }}
|
{{ v$.username.$errors[0].$message }}
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<input type="text" id="input-username" class="form-control" :class="[{'border-danger': v$.username.$error}]" v-model="username" @blur="v$.username.$touch">
|
<input type="text" id="input-username" class="form-control"
|
||||||
|
:class="[{ 'border-danger': v$.username.$error }]" v-model="username" @blur="v$.username.$touch" @keyup.enter="signupUser">
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="input-username">
|
<label for="input-username">
|
||||||
{{ t( 'login.password' ) }}
|
{{ t( 'login.password' ) }}
|
||||||
<span v-if="v$.password.$error" class="text-danger ps-3">
|
<span v-if=" v$.password.$error " class="text-danger ps-3">
|
||||||
{{ v$.password.$errors[0].$message }}
|
{{ v$.password.$errors[0].$message }}
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<input type="password" id="input-username" class="form-control" :class="[{'border-danger': v$.password.$error}]" v-model="password" @blur="v$.password.$touch">
|
<input type="password" id="input-username" class="form-control"
|
||||||
|
:class="[{ 'border-danger': v$.password.$error }]" v-model="password" @blur="v$.password.$touch" @keyup.enter="signupUser">
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="input-username-repeat">
|
<label for="input-username-repeat">
|
||||||
{{ t( 'signup.password-repeat' ) }}
|
{{ t( 'signup.password-repeat' ) }}
|
||||||
<span v-if="v$.passwordRepeat.$error" class="text-danger ps-3">
|
<span v-if=" v$.passwordRepeat.$error " class="text-danger ps-3">
|
||||||
{{ v$.passwordRepeat.$errors[0].$message }}
|
{{ v$.passwordRepeat.$errors[0].$message }}
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<input type="password" id="input-username-repeat" class="form-control" :class="[{'border-danger': v$.passwordRepeat.$error}]" v-model="passwordRepeat" @blur="v$.passwordRepeat.$touch">
|
<input type="password" id="input-username-repeat" class="form-control"
|
||||||
|
:class="[{ 'border-danger': v$.passwordRepeat.$error }]" v-model="passwordRepeat"
|
||||||
|
@blur="v$.passwordRepeat.$touch" @keyup.enter="signupUser">
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3 d-flex justify-content-between">
|
<div class="mb-3 d-flex justify-content-between">
|
||||||
<RouterLink to="/login" class="btn btn-outline-primary">
|
<RouterLink to="/login" class="btn btn-outline-primary">
|
||||||
{{ t( "signup.loginLinkButton" ) }}
|
{{ t( "signup.loginLinkButton" ) }}
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
<button class="btn btn-primary" @click="signupUser" :disabled="signupInProgress">
|
<button class="btn btn-primary" @click="signupUser" :disabled="signupInProgress">
|
||||||
<font-awesome-icon v-if="signupInProgress" :icon="['fas', 'spinner']" spin/>
|
<FontAwesomeIcon v-if=" signupInProgress " :icon="['fas', 'spinner']" spin />
|
||||||
{{ t( "signup.signupButton" ) }}
|
{{ t( "signup.signupButton" ) }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="errorMessage" class="alert alert-danger" role="alert">{{ errorMessage }}</div>
|
<div v-if=" errorMessage " class="alert alert-danger" role="alert">{{ errorMessage }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,8 @@
|
||||||
"username": "Username",
|
"username": "Username",
|
||||||
"password": "Password",
|
"password": "Password",
|
||||||
"error": {
|
"error": {
|
||||||
"process": "An error occured during the login process"
|
"process": "An error occured during the login process",
|
||||||
|
"credentials": "Username or password incorrect"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"signup": {
|
"signup": {
|
||||||
|
|
@ -37,14 +38,19 @@
|
||||||
"profile": {
|
"profile": {
|
||||||
"yourProfile": "Your Profile",
|
"yourProfile": "Your Profile",
|
||||||
"yourBoards": "Your Boards",
|
"yourBoards": "Your Boards",
|
||||||
"gotoSettings": "Go to Settings"
|
"gotoSettings": "Go to Settings",
|
||||||
|
"edit": {
|
||||||
|
"title": "Edit your Profile"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"heading": "Settings"
|
"heading": "Settings"
|
||||||
},
|
},
|
||||||
"common": {
|
"common": {
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"close": "Close"
|
"close": "Close",
|
||||||
|
"save": "Save",
|
||||||
|
"saveAndExit": "Save and Exit"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"generic": "Error"
|
"generic": "Error"
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,9 @@ import router from './router';
|
||||||
|
|
||||||
import '@/assets/scss/customized_bootstrap.scss';
|
import '@/assets/scss/customized_bootstrap.scss';
|
||||||
|
|
||||||
// Importing bootstrap components which rely on js here
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
import { Dropdown } from 'bootstrap';
|
|
||||||
|
|
||||||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
|
||||||
import { library } from '@fortawesome/fontawesome-svg-core';
|
import { library } from '@fortawesome/fontawesome-svg-core';
|
||||||
import { faSun, faMoon, faCircleHalfStroke, faEdit, faPlay, faSpinner } from '@fortawesome/free-solid-svg-icons';
|
import { faSun, faMoon, faCircleHalfStroke, faEdit, faPlay, faSpinner, faLanguage, faGlobe } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
import enMessages from './locales/en.json';
|
import enMessages from './locales/en.json';
|
||||||
import deMessages from './locales/de.json';
|
import deMessages from './locales/de.json';
|
||||||
|
|
@ -34,7 +30,9 @@ library.add(
|
||||||
faCircleHalfStroke,
|
faCircleHalfStroke,
|
||||||
faEdit,
|
faEdit,
|
||||||
faPlay,
|
faPlay,
|
||||||
faSpinner
|
faSpinner,
|
||||||
|
faLanguage,
|
||||||
|
faGlobe,
|
||||||
)
|
)
|
||||||
|
|
||||||
const app = createApp( App );
|
const app = createApp( App );
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,4 @@
|
||||||
// export class LoginDto{
|
|
||||||
// username: String;
|
|
||||||
// password: String;
|
|
||||||
|
|
||||||
// constructor(username: String, password: String){
|
|
||||||
// this.username = username;
|
|
||||||
// this.password = password;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
export type LoginDto = {
|
export type LoginDto = {
|
||||||
username: String;
|
username: string;
|
||||||
password: String;
|
password: string;
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
export type User = {
|
export type User = {
|
||||||
username: string,
|
username: string,
|
||||||
password: string,
|
|
||||||
profilePictureFilename: string | undefined,
|
profilePictureFilename: string | undefined,
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
// This can be directly added to any of your `.ts` files like `router.ts`
|
||||||
|
// It can also be added to a `.d.ts` file. Make sure it's included in
|
||||||
|
// project's tsconfig.json "files"
|
||||||
|
import 'vue-router'
|
||||||
|
|
||||||
|
// To ensure it is treated as a module, add at least one `export` statement
|
||||||
|
export {}
|
||||||
|
|
||||||
|
declare module 'vue-router' {
|
||||||
|
interface RouteMeta {
|
||||||
|
// must be declared by every route
|
||||||
|
requiresAuth: boolean
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,7 @@ import LoginPage from '@/components/pages/LoginPage.vue';
|
||||||
import SignupPage from '@/components/pages/SignupPage.vue';
|
import SignupPage from '@/components/pages/SignupPage.vue';
|
||||||
import GamePage from '@/components/pages/GamePage.vue';
|
import GamePage from '@/components/pages/GamePage.vue';
|
||||||
import ProfilePage from '@/components/pages/ProfilePage.vue';
|
import ProfilePage from '@/components/pages/ProfilePage.vue';
|
||||||
|
import { useUserStore } from '@/stores/UserStore';
|
||||||
|
|
||||||
const router = createRouter( {
|
const router = createRouter( {
|
||||||
history: createWebHistory( import.meta.env.BASE_URL ),
|
history: createWebHistory( import.meta.env.BASE_URL ),
|
||||||
|
|
@ -13,31 +14,57 @@ const router = createRouter( {
|
||||||
path: '/',
|
path: '/',
|
||||||
name: 'home',
|
name: 'home',
|
||||||
component: HomePage,
|
component: HomePage,
|
||||||
|
meta: {
|
||||||
|
requiresAuth: false,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/about',
|
path: '/about',
|
||||||
name: 'about',
|
name: 'about',
|
||||||
component: AboutPage,
|
component: AboutPage,
|
||||||
|
meta: {
|
||||||
|
requiresAuth: false,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/login',
|
path: '/login',
|
||||||
name: 'login',
|
name: 'login',
|
||||||
component: LoginPage,
|
component: LoginPage,
|
||||||
|
meta: {
|
||||||
|
requiresAuth: false,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/signup',
|
path: '/signup',
|
||||||
name: 'signup',
|
name: 'signup',
|
||||||
component: SignupPage,
|
component: SignupPage,
|
||||||
|
meta: {
|
||||||
|
requiresAuth: false,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/profile',
|
path: '/profile',
|
||||||
name: 'profile',
|
name: 'profile',
|
||||||
component: ProfilePage,
|
component: ProfilePage,
|
||||||
|
meta: {
|
||||||
|
requiresAuth: true,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/game',
|
path: '/game',
|
||||||
name: 'Game',
|
name: 'game',
|
||||||
component: GamePage,
|
component: GamePage,
|
||||||
|
meta: {
|
||||||
|
requiresAuth: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/board',
|
||||||
|
name: 'board',
|
||||||
|
component: ProfilePage,
|
||||||
|
meta: {
|
||||||
|
requiresAuth: true,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
// path: '/about',
|
// path: '/about',
|
||||||
|
|
@ -47,7 +74,23 @@ const router = createRouter( {
|
||||||
// // which is lazy-loaded when the route is visited.
|
// // which is lazy-loaded when the route is visited.
|
||||||
// component: () => import('../views/AboutView.vue')
|
// component: () => import('../views/AboutView.vue')
|
||||||
// }
|
// }
|
||||||
]
|
],
|
||||||
|
} );
|
||||||
|
|
||||||
|
router.beforeEach( ( to, from, next ) => {
|
||||||
|
const userStore = useUserStore();
|
||||||
|
userStore.userCheckPromise
|
||||||
|
.finally( () => {
|
||||||
|
if( to.meta.requiresAuth === true && !userStore.loggedIn ) {
|
||||||
|
if( from.name === 'login' ) {
|
||||||
|
console.error( 'recursive forward detected' );
|
||||||
|
next( { name: 'home' } );
|
||||||
|
}
|
||||||
|
next( { name: 'login', query: { r: to.name?.toString() } } );
|
||||||
|
} else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
} );
|
||||||
} );
|
} );
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import { ENV } from "@/Env";
|
import { ENV } from "@/Env";
|
||||||
import type { LoginDto } from '@/models/dto/LoginDto';
|
import type { LoginDto } from '@/models/dto/LoginDto';
|
||||||
import axios from "axios";
|
import type { User } from '@/models/user/User';
|
||||||
|
import axios, { AxiosError } from "axios";
|
||||||
|
|
||||||
class AuthService {
|
class AuthService {
|
||||||
|
|
||||||
signupUser( signupDto: LoginDto ) {
|
signupUser( signupDto: LoginDto ): Promise<User> {
|
||||||
return new Promise( ( resolve, reject ) => {
|
return new Promise( ( resolve, reject ) => {
|
||||||
axios.post( `${ENV.API_BASE_URL}/auth/signup`,
|
axios.post( `${ENV.API_BASE_URL}/auth/signup`,
|
||||||
signupDto,
|
signupDto,
|
||||||
|
|
@ -16,13 +16,13 @@ class AuthService {
|
||||||
.then( ( response ) => {
|
.then( ( response ) => {
|
||||||
resolve( response.data );
|
resolve( response.data );
|
||||||
} )
|
} )
|
||||||
.catch( ( error ) => {
|
.catch( ( error: Error | AxiosError ) => {
|
||||||
reject( error );
|
reject( error );
|
||||||
} );
|
} );
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
loginUser( loginDto: LoginDto ) {
|
loginUser( loginDto: LoginDto ): Promise<User> {
|
||||||
return new Promise( ( resolve, reject ) => {
|
return new Promise( ( resolve, reject ) => {
|
||||||
axios.post( `${ENV.API_BASE_URL}/auth/login`,
|
axios.post( `${ENV.API_BASE_URL}/auth/login`,
|
||||||
loginDto,
|
loginDto,
|
||||||
|
|
@ -31,15 +31,15 @@ class AuthService {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.then( ( response ) => {
|
.then( ( response ) => {
|
||||||
resolve( response );
|
resolve( response.data );
|
||||||
} )
|
} )
|
||||||
.catch( ( error ) => {
|
.catch( ( error: Error | AxiosError ) => {
|
||||||
reject( error );
|
reject( error );
|
||||||
} );
|
} );
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
logoutUser() {
|
logoutUser(): Promise<string> {
|
||||||
return new Promise( ( resolve, reject ) => {
|
return new Promise( ( resolve, reject ) => {
|
||||||
axios.post( `${ENV.API_BASE_URL}/auth/logout`,
|
axios.post( `${ENV.API_BASE_URL}/auth/logout`,
|
||||||
null,
|
null,
|
||||||
|
|
@ -48,9 +48,25 @@ class AuthService {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.then( ( response ) => {
|
.then( ( response ) => {
|
||||||
resolve( response );
|
resolve( response.data );
|
||||||
} )
|
} )
|
||||||
.catch( ( error ) => {
|
.catch( ( error: Error | AxiosError ) => {
|
||||||
|
reject( error );
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
checkUser(): Promise<User> {
|
||||||
|
return new Promise( ( resolve, reject ) => {
|
||||||
|
axios.get( `${ENV.API_BASE_URL}/auth/status`,
|
||||||
|
{
|
||||||
|
withCredentials: true,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.then( ( response ) => {
|
||||||
|
resolve( response.data );
|
||||||
|
} )
|
||||||
|
.catch( ( error: Error | AxiosError ) => {
|
||||||
reject( error );
|
reject( error );
|
||||||
} );
|
} );
|
||||||
} );
|
} );
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,3 @@
|
||||||
import type { InjectionKey } from 'vue';
|
import type { InjectionKey } from 'vue';
|
||||||
|
|
||||||
class UtilService{
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export const infoModalShowFnKey = Symbol() as InjectionKey<Function>;
|
export const infoModalShowFnKey = Symbol() as InjectionKey<Function>;
|
||||||
|
|
@ -1,38 +1,59 @@
|
||||||
import { computed, ref } from "vue";
|
import { computed, ref } from "vue";
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
import type { User } from '@/models/user/User';
|
import type { User } from '@/models/user/User';
|
||||||
|
import { authService } from '@/services/AuthService';
|
||||||
|
import { AxiosError } from 'axios';
|
||||||
|
|
||||||
export const useUserStore = defineStore( 'user', () => {
|
export const useUserStore = defineStore( 'user', () => {
|
||||||
const username = ref( '' );
|
const username = ref( '' );
|
||||||
const profilePicture = ref<undefined | string>( undefined );
|
const profilePicture = ref<null | string>( null );
|
||||||
const loggedIn = ref(false);
|
const loggedIn = ref( false );
|
||||||
|
|
||||||
const getUserOutput = computed(() => `${username.value}`);
|
const getUserOutput = computed( () => `${username.value}` );
|
||||||
|
const pfpSource = computed( () => {
|
||||||
|
return profilePicture.value ?? "/src/assets/images/PFP_BearHead.svg"
|
||||||
|
})
|
||||||
|
|
||||||
function setUser(user: User){
|
function loginUser( user: User ) {
|
||||||
username.value = user.username;
|
username.value = user.username;
|
||||||
profilePicture.value = user.profilePictureFilename;
|
profilePicture.value = user.profilePictureFilename ?? null;
|
||||||
loggedIn.value = true;
|
loggedIn.value = true;
|
||||||
sessionStorage.setItem() //???
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function unsetUser(){
|
function logoutUser() {
|
||||||
username.value = '';
|
username.value = '';
|
||||||
profilePicture.value = undefined;
|
profilePicture.value = null;
|
||||||
loggedIn.value = false;
|
loggedIn.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getCheckUser(): Promise<User> {
|
||||||
|
return new Promise( ( resolve, reject ) => {
|
||||||
|
authService.checkUser()
|
||||||
|
.then( ( user ) => {
|
||||||
|
loginUser( user );
|
||||||
|
resolve( user );
|
||||||
|
} )
|
||||||
|
.catch( ( error: Error | AxiosError ) => {
|
||||||
|
console.debug( error );
|
||||||
|
reject( error );
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
const userCheckPromise = getCheckUser();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
//Refs
|
//Refs
|
||||||
username,
|
username,
|
||||||
profilePicture,
|
profilePicture,
|
||||||
loggedIn,
|
loggedIn,
|
||||||
|
userCheckPromise,
|
||||||
|
|
||||||
//Getters
|
//Getters
|
||||||
getUserOutput,
|
getUserOutput,
|
||||||
|
pfpSource,
|
||||||
|
|
||||||
//Functions
|
//Functions
|
||||||
setUser,
|
loginUser,
|
||||||
unsetUser,
|
logoutUser,
|
||||||
};
|
};
|
||||||
} );
|
} );
|
||||||
Loading…
Reference in New Issue