Added auth stuff to the web app.
This commit is contained in:
parent
0e96d74203
commit
f8dcfc8843
|
@ -2,6 +2,7 @@ package nl.andrewlalis.gymboard_api.config;
|
||||||
|
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
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;
|
||||||
|
@ -9,6 +10,9 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe
|
||||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
|
import org.springframework.web.cors.CorsConfiguration;
|
||||||
|
import org.springframework.web.cors.CorsConfigurationSource;
|
||||||
|
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
|
@ -20,17 +24,44 @@ public class SecurityConfig {
|
||||||
this.tokenAuthenticationFilter = tokenAuthenticationFilter;
|
this.tokenAuthenticationFilter = tokenAuthenticationFilter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines the security configuration we'll use for this API.
|
||||||
|
* @param http The security configurable.
|
||||||
|
* @return The filter chain to apply.
|
||||||
|
* @throws Exception If an error occurs while configuring.
|
||||||
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||||
http
|
http
|
||||||
.httpBasic().disable()
|
.httpBasic().disable()
|
||||||
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
|
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
|
||||||
.csrf().disable()
|
.csrf().disable()
|
||||||
|
.cors().and()
|
||||||
.addFilterBefore(tokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
|
.addFilterBefore(tokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
|
||||||
.authorizeHttpRequests().anyRequest().permitAll();
|
.authorizeHttpRequests().anyRequest().permitAll();
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines the CORS configuration for this API, which is to say that we
|
||||||
|
* allow cross-origin requests ONLY from the web app for the vast majority
|
||||||
|
* of endpoints.
|
||||||
|
* @return The CORS configuration source.
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
@Order(1)
|
||||||
|
public CorsConfigurationSource corsConfigurationSource() {
|
||||||
|
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||||
|
final CorsConfiguration config = new CorsConfiguration();
|
||||||
|
config.setAllowCredentials(true);
|
||||||
|
// Don't do this in production, use a proper list of allowed origins
|
||||||
|
config.addAllowedOriginPattern("*");
|
||||||
|
config.addAllowedHeader("*");
|
||||||
|
config.addAllowedMethod("*");
|
||||||
|
source.registerCorsConfiguration("/**", config);
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public AuthenticationManager authenticationManager() {
|
public AuthenticationManager authenticationManager() {
|
||||||
return null;// Disable the standard spring authentication manager.
|
return null;// Disable the standard spring authentication manager.
|
||||||
|
|
|
@ -1,31 +1,11 @@
|
||||||
package nl.andrewlalis.gymboard_api.config;
|
package nl.andrewlalis.gymboard_api.config;
|
||||||
|
|
||||||
import nl.andrewlalis.gymboard_api.util.ULID;
|
import nl.andrewlalis.gymboard_api.util.ULID;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.web.cors.CorsConfiguration;
|
|
||||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
|
||||||
import org.springframework.web.filter.CorsFilter;
|
|
||||||
|
|
||||||
import java.time.OffsetDateTime;
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
public class WebConfig {
|
public class WebConfig {
|
||||||
@Bean
|
|
||||||
public CorsFilter corsFilter() {
|
|
||||||
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
|
||||||
final CorsConfiguration config = new CorsConfiguration();
|
|
||||||
config.setAllowCredentials(true);
|
|
||||||
// Don't do this in production, use a proper list of allowed origins
|
|
||||||
config.addAllowedOriginPattern("*");
|
|
||||||
config.setAllowedHeaders(Arrays.asList("Origin", "Content-Type", "Accept"));
|
|
||||||
config.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "OPTIONS", "DELETE", "PATCH"));
|
|
||||||
source.registerCorsConfiguration("/**", config);
|
|
||||||
return new CorsFilter(source);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public ULID ulid() {
|
public ULID ulid() {
|
||||||
return new ULID();
|
return new ULID();
|
||||||
|
|
|
@ -2,7 +2,12 @@ package nl.andrewlalis.gymboard_api.controller;
|
||||||
|
|
||||||
import nl.andrewlalis.gymboard_api.controller.dto.TokenCredentials;
|
import nl.andrewlalis.gymboard_api.controller.dto.TokenCredentials;
|
||||||
import nl.andrewlalis.gymboard_api.controller.dto.TokenResponse;
|
import nl.andrewlalis.gymboard_api.controller.dto.TokenResponse;
|
||||||
|
import nl.andrewlalis.gymboard_api.controller.dto.UserResponse;
|
||||||
|
import nl.andrewlalis.gymboard_api.model.auth.User;
|
||||||
import nl.andrewlalis.gymboard_api.service.auth.TokenService;
|
import nl.andrewlalis.gymboard_api.service.auth.TokenService;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||||
|
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.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
@ -19,4 +24,10 @@ public class AuthController {
|
||||||
public TokenResponse getToken(@RequestBody TokenCredentials credentials) {
|
public TokenResponse getToken(@RequestBody TokenCredentials credentials) {
|
||||||
return tokenService.generateAccessToken(credentials);
|
return tokenService.generateAccessToken(credentials);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping(path = "/auth/me")
|
||||||
|
@PreAuthorize("isAuthenticated()")
|
||||||
|
public UserResponse getMyUser(@AuthenticationPrincipal User user) {
|
||||||
|
return new UserResponse(user);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { api } from 'src/api/main/index';
|
||||||
|
|
||||||
|
export interface User {
|
||||||
|
id: string;
|
||||||
|
activated: boolean;
|
||||||
|
email: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TokenCredentials {
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
class AuthModule {
|
||||||
|
public async getToken(credentials: TokenCredentials): Promise<string> {
|
||||||
|
const response = await api.post('/auth/token', credentials);
|
||||||
|
return response.data.token;
|
||||||
|
}
|
||||||
|
public async getMyUser(token: string): Promise<User> {
|
||||||
|
const response = await api.get('/auth/me', {
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer ' + token
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AuthModule;
|
|
@ -2,6 +2,7 @@ import axios from 'axios';
|
||||||
import GymsModule from 'src/api/main/gyms';
|
import GymsModule from 'src/api/main/gyms';
|
||||||
import ExercisesModule from 'src/api/main/exercises';
|
import ExercisesModule from 'src/api/main/exercises';
|
||||||
import LeaderboardsModule from 'src/api/main/leaderboards';
|
import LeaderboardsModule from 'src/api/main/leaderboards';
|
||||||
|
import AuthModule from 'src/api/main/auth';
|
||||||
|
|
||||||
export const BASE_URL = 'http://localhost:8080';
|
export const BASE_URL = 'http://localhost:8080';
|
||||||
|
|
||||||
|
@ -11,6 +12,7 @@ export const api = axios.create({
|
||||||
});
|
});
|
||||||
|
|
||||||
class GymboardApi {
|
class GymboardApi {
|
||||||
|
public readonly auth = new AuthModule();
|
||||||
public readonly gyms = new GymsModule();
|
public readonly gyms = new GymsModule();
|
||||||
public readonly exercises = new ExercisesModule();
|
public readonly exercises = new ExercisesModule();
|
||||||
public readonly leaderboards = new LeaderboardsModule();
|
public readonly leaderboards = new LeaderboardsModule();
|
||||||
|
|
|
@ -41,8 +41,9 @@
|
||||||
<q-item-label header>
|
<q-item-label header>
|
||||||
{{ $t('mainLayout.pages') }}
|
{{ $t('mainLayout.pages') }}
|
||||||
</q-item-label>
|
</q-item-label>
|
||||||
<q-item clickable>Gyms</q-item>
|
<q-item clickable to="/">Gyms</q-item>
|
||||||
<q-item clickable>Global Leaderboard</q-item>
|
<q-item clickable>Global Leaderboard</q-item>
|
||||||
|
<q-item clickable to="/testing">Testing Page</q-item>
|
||||||
</q-list>
|
</q-list>
|
||||||
</q-drawer>
|
</q-drawer>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
<template>
|
||||||
|
<StandardCenteredPage>
|
||||||
|
<h3>Testing Page</h3>
|
||||||
|
<p>
|
||||||
|
Use this page to test new functionality, before adding it to the main
|
||||||
|
app. This page should be hidden on production.
|
||||||
|
</p>
|
||||||
|
<div class="row" style="border: 3px solid red">
|
||||||
|
<h4>Auth Test</h4>
|
||||||
|
<q-btn
|
||||||
|
label="Do auth"
|
||||||
|
@click="doAuth()"
|
||||||
|
/>
|
||||||
|
<p>{{ authTestMessage }}</p>
|
||||||
|
</div>
|
||||||
|
</StandardCenteredPage>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import StandardCenteredPage from 'src/components/StandardCenteredPage.vue';
|
||||||
|
import api from 'src/api/main';
|
||||||
|
import {ref} from 'vue';
|
||||||
|
import {sleep} from "src/utils";
|
||||||
|
|
||||||
|
const authTestMessage = ref('');
|
||||||
|
|
||||||
|
async function doAuth() {
|
||||||
|
const token = await api.auth.getToken({email: 'andrew.lalis@example.com', password: 'testpass'});
|
||||||
|
authTestMessage.value = 'Token: ' + token;
|
||||||
|
await sleep(2000);
|
||||||
|
const user = await api.auth.getMyUser(token);
|
||||||
|
authTestMessage.value = 'User: ' + JSON.stringify(user);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -5,6 +5,7 @@ import GymPage from 'pages/gym/GymPage.vue';
|
||||||
import GymSubmissionPage from 'pages/gym/GymSubmissionPage.vue';
|
import GymSubmissionPage from 'pages/gym/GymSubmissionPage.vue';
|
||||||
import GymHomePage from 'pages/gym/GymHomePage.vue';
|
import GymHomePage from 'pages/gym/GymHomePage.vue';
|
||||||
import GymLeaderboardsPage from 'pages/gym/GymLeaderboardsPage.vue';
|
import GymLeaderboardsPage from 'pages/gym/GymLeaderboardsPage.vue';
|
||||||
|
import TestingPage from 'pages/TestingPage.vue';
|
||||||
|
|
||||||
const routes: RouteRecordRaw[] = [
|
const routes: RouteRecordRaw[] = [
|
||||||
{
|
{
|
||||||
|
@ -12,6 +13,7 @@ const routes: RouteRecordRaw[] = [
|
||||||
component: MainLayout,
|
component: MainLayout,
|
||||||
children: [
|
children: [
|
||||||
{ path: '', component: IndexPage },
|
{ path: '', component: IndexPage },
|
||||||
|
{ path: 'testing', component: TestingPage },
|
||||||
{
|
{
|
||||||
path: 'gyms/:countryCode/:cityShortName/:gymShortName',
|
path: 'gyms/:countryCode/:cityShortName/:gymShortName',
|
||||||
component: GymPage,
|
component: GymPage,
|
||||||
|
|
Loading…
Reference in New Issue