Merge branch '46-design-activities' of https://gitlab.com/marcel.schwarz/2020ss-qbc-geofence-timetracking into 46-design-activities
This commit is contained in:
commit
57f5ba4b03
29
.gitlab-ci.yml
Normal file
29
.gitlab-ci.yml
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
build-vue:
|
||||||
|
image: docker:latest
|
||||||
|
stage: build
|
||||||
|
services:
|
||||||
|
- docker:dind
|
||||||
|
script:
|
||||||
|
- cd frontend
|
||||||
|
- docker build --pull -t vue .
|
||||||
|
rules:
|
||||||
|
- if: $CI_MERGE_REQUEST_ID
|
||||||
|
|
||||||
|
build-backend:
|
||||||
|
image: docker:latest
|
||||||
|
stage: build
|
||||||
|
services:
|
||||||
|
- docker:dind
|
||||||
|
script:
|
||||||
|
- cd backend
|
||||||
|
- docker build --pull -t backend .
|
||||||
|
rules:
|
||||||
|
- if: $CI_MERGE_REQUEST_ID
|
||||||
|
|
||||||
|
build-android:
|
||||||
|
image: debian
|
||||||
|
stage: build
|
||||||
|
script:
|
||||||
|
- echo "To be done"
|
||||||
|
rules:
|
||||||
|
- if: $CI_MERGE_REQUEST_ID
|
@ -1,26 +1,32 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'org.springframework.boot' version '2.2.6.RELEASE'
|
id "org.springframework.boot" version "2.2.6.RELEASE"
|
||||||
id 'io.spring.dependency-management' version '1.0.9.RELEASE'
|
id "io.spring.dependency-management" version "1.0.9.RELEASE"
|
||||||
id "io.freefair.lombok" version "5.0.0-rc6"
|
id "io.freefair.lombok" version "5.0.0"
|
||||||
id 'java'
|
id "java"
|
||||||
}
|
}
|
||||||
|
|
||||||
group = 'de.hft'
|
group = "de.hft"
|
||||||
version = '0.0.1-SNAPSHOT'
|
version = "0.0.1-SNAPSHOT"
|
||||||
sourceCompatibility = '11'
|
sourceCompatibility = "11"
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-data-rest'
|
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-actuator'
|
implementation 'org.springframework.boot:spring-boot-starter-actuator'
|
||||||
|
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
||||||
|
compileOnly 'org.projectlombok:lombok'
|
||||||
|
annotationProcessor 'org.projectlombok:lombok'
|
||||||
implementation 'org.mariadb.jdbc:mariadb-java-client'
|
implementation 'org.mariadb.jdbc:mariadb-java-client'
|
||||||
|
|
||||||
|
implementation "org.springframework.boot:spring-boot-starter-security"
|
||||||
|
implementation "com.auth0:java-jwt:3.10.2"
|
||||||
|
|
||||||
runtimeOnly 'com.h2database:h2'
|
runtimeOnly 'com.h2database:h2'
|
||||||
|
|
||||||
testImplementation('org.springframework.boot:spring-boot-starter-test') {
|
testImplementation('org.springframework.boot:spring-boot-starter-test') {
|
||||||
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
|
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,9 @@ package de.hft.geotime;
|
|||||||
|
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.ComponentScan;
|
import org.springframework.context.annotation.ComponentScan;
|
||||||
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
@ComponentScan(basePackages = "de.hft")
|
@ComponentScan(basePackages = "de.hft")
|
||||||
@ -12,4 +14,9 @@ public class GeotimeApplication {
|
|||||||
SpringApplication.run(GeotimeApplication.class, args);
|
SpringApplication.run(GeotimeApplication.class, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public BCryptPasswordEncoder bCryptPasswordEncoder() {
|
||||||
|
return new BCryptPasswordEncoder();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
package de.hft.geotime.record;
|
||||||
|
|
||||||
|
import org.springframework.data.repository.CrudRepository;
|
||||||
|
|
||||||
|
public interface RecordRepository extends CrudRepository<TimeRecord, Long> {
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
package de.hft.geotime.record;
|
||||||
|
|
||||||
|
public enum RecordType {
|
||||||
|
|
||||||
|
BREAK,
|
||||||
|
PAID
|
||||||
|
|
||||||
|
}
|
29
backend/src/main/java/de/hft/geotime/record/TimeRecord.java
Normal file
29
backend/src/main/java/de/hft/geotime/record/TimeRecord.java
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package de.hft.geotime.record;
|
||||||
|
|
||||||
|
import de.hft.geotime.timetrackaccount.TimetrackAccount;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.OneToOne;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Entity
|
||||||
|
public class TimeRecord {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
private long id;
|
||||||
|
@OneToOne
|
||||||
|
private TimetrackAccount account;
|
||||||
|
private Date startdate;
|
||||||
|
private Date enddate;
|
||||||
|
private Duration time;
|
||||||
|
private RecordType type;
|
||||||
|
|
||||||
|
}
|
24
backend/src/main/java/de/hft/geotime/role/Role.java
Normal file
24
backend/src/main/java/de/hft/geotime/role/Role.java
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package de.hft.geotime.role;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.GeneratedValue;
|
||||||
|
import javax.persistence.GenerationType;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Entity
|
||||||
|
public class Role {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||||
|
private long id;
|
||||||
|
private String name;
|
||||||
|
// TODO: Permission List
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package de.hft.geotime.role;
|
||||||
|
|
||||||
|
import org.springframework.data.repository.CrudRepository;
|
||||||
|
|
||||||
|
public interface RoleRepository extends CrudRepository<Role, Long> {
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
package de.hft.geotime.security;
|
||||||
|
|
||||||
|
import com.auth0.jwt.JWT;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import de.hft.geotime.user.TimetrackUser;
|
||||||
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.AuthenticationException;
|
||||||
|
import org.springframework.security.core.userdetails.User;
|
||||||
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
|
|
||||||
|
import javax.servlet.FilterChain;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import static com.auth0.jwt.algorithms.Algorithm.HMAC512;
|
||||||
|
import static de.hft.geotime.security.SecurityConstants.*;
|
||||||
|
|
||||||
|
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
|
||||||
|
private final AuthenticationManager authenticationManager;
|
||||||
|
|
||||||
|
public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
|
||||||
|
this.authenticationManager = authenticationManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Authentication attemptAuthentication(
|
||||||
|
HttpServletRequest req,
|
||||||
|
HttpServletResponse res) throws AuthenticationException {
|
||||||
|
try {
|
||||||
|
TimetrackUser creds = new ObjectMapper().readValue(req.getInputStream(), TimetrackUser.class);
|
||||||
|
return authenticationManager.authenticate(
|
||||||
|
new UsernamePasswordAuthenticationToken(
|
||||||
|
creds.getUsername(),
|
||||||
|
creds.getPassword(),
|
||||||
|
new ArrayList<>()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void successfulAuthentication(
|
||||||
|
HttpServletRequest req,
|
||||||
|
HttpServletResponse res,
|
||||||
|
FilterChain chain,
|
||||||
|
Authentication auth) {
|
||||||
|
String token = JWT.create()
|
||||||
|
.withSubject(((User) auth.getPrincipal()).getUsername())
|
||||||
|
.withExpiresAt(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
|
||||||
|
.sign(HMAC512(SECRET.getBytes()));
|
||||||
|
res.addHeader(HEADER_STRING, TOKEN_PREFIX + token);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
package de.hft.geotime.security;
|
||||||
|
|
||||||
|
import com.auth0.jwt.JWT;
|
||||||
|
import com.auth0.jwt.algorithms.Algorithm;
|
||||||
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
|
||||||
|
|
||||||
|
import javax.servlet.FilterChain;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import static de.hft.geotime.security.SecurityConstants.*;
|
||||||
|
|
||||||
|
public class JWTAuthorizationFilter extends BasicAuthenticationFilter {
|
||||||
|
|
||||||
|
public JWTAuthorizationFilter(AuthenticationManager authManager) {
|
||||||
|
super(authManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {
|
||||||
|
String header = req.getHeader(HEADER_STRING);
|
||||||
|
|
||||||
|
if (header == null || !header.startsWith(TOKEN_PREFIX)) {
|
||||||
|
chain.doFilter(req, res);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UsernamePasswordAuthenticationToken authentication = getAuthentication(req);
|
||||||
|
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||||
|
chain.doFilter(req, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
|
||||||
|
String token = request.getHeader(HEADER_STRING);
|
||||||
|
if (token != null) {
|
||||||
|
// parse the token.
|
||||||
|
String user = JWT.require(Algorithm.HMAC512(SECRET.getBytes()))
|
||||||
|
.build()
|
||||||
|
.verify(token.replace(TOKEN_PREFIX, ""))
|
||||||
|
.getSubject();
|
||||||
|
|
||||||
|
if (user != null) {
|
||||||
|
return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package de.hft.geotime.security;
|
||||||
|
|
||||||
|
public class SecurityConstants {
|
||||||
|
public static final String SECRET = "SecretKeyToGenJWTs";
|
||||||
|
public static final long EXPIRATION_TIME = 864_000_000; // 10 days
|
||||||
|
public static final String TOKEN_PREFIX = "Bearer ";
|
||||||
|
public static final String HEADER_STRING = "Authorization";
|
||||||
|
public static final String SIGN_UP_URL = "/user/sign-up";
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
package de.hft.geotime.security;
|
||||||
|
|
||||||
|
import de.hft.geotime.user.UserDetailsServiceImpl;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||||
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
|
import org.springframework.web.cors.CorsConfiguration;
|
||||||
|
import org.springframework.web.cors.CorsConfigurationSource;
|
||||||
|
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||||
|
|
||||||
|
import static de.hft.geotime.security.SecurityConstants.SIGN_UP_URL;
|
||||||
|
|
||||||
|
@EnableWebSecurity
|
||||||
|
public class WebSecurity extends WebSecurityConfigurerAdapter {
|
||||||
|
private final UserDetailsServiceImpl userDetailsService;
|
||||||
|
private final BCryptPasswordEncoder bCryptPasswordEncoder;
|
||||||
|
|
||||||
|
public WebSecurity(UserDetailsServiceImpl userDetailsService, BCryptPasswordEncoder bCryptPasswordEncoder) {
|
||||||
|
this.userDetailsService = userDetailsService;
|
||||||
|
this.bCryptPasswordEncoder = bCryptPasswordEncoder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void configure(HttpSecurity http) throws Exception {
|
||||||
|
http.cors().and().csrf().disable().authorizeRequests()
|
||||||
|
.antMatchers(HttpMethod.POST, SIGN_UP_URL).permitAll()
|
||||||
|
.anyRequest().authenticated()
|
||||||
|
.and()
|
||||||
|
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
|
||||||
|
.addFilter(new JWTAuthorizationFilter(authenticationManager()))
|
||||||
|
// this disables session creation on Spring Security
|
||||||
|
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(AuthenticationManagerBuilder auth) throws Exception {
|
||||||
|
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
CorsConfigurationSource corsConfigurationSource() {
|
||||||
|
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||||
|
source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package de.hft.geotime.timetrackaccount;
|
||||||
|
|
||||||
|
import de.hft.geotime.user.TimetrackUser;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import javax.persistence.*;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Entity
|
||||||
|
public class TimetrackAccount {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||||
|
private long id;
|
||||||
|
@OneToOne
|
||||||
|
private TimetrackUser timetrackUser;
|
||||||
|
private double revenue;
|
||||||
|
private String name;
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package de.hft.geotime.timetrackaccount;
|
||||||
|
|
||||||
|
import org.springframework.data.repository.CrudRepository;
|
||||||
|
|
||||||
|
public interface TimetrackAccountRepository extends CrudRepository<TimetrackAccount, Long> {
|
||||||
|
|
||||||
|
}
|
32
backend/src/main/java/de/hft/geotime/user/TimetrackUser.java
Normal file
32
backend/src/main/java/de/hft/geotime/user/TimetrackUser.java
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package de.hft.geotime.user;
|
||||||
|
|
||||||
|
import de.hft.geotime.role.Role;
|
||||||
|
import de.hft.geotime.timetrackaccount.TimetrackAccount;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.hibernate.validator.constraints.UniqueElements;
|
||||||
|
|
||||||
|
import javax.persistence.*;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Entity
|
||||||
|
public class TimetrackUser {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||||
|
private long id;
|
||||||
|
@UniqueElements
|
||||||
|
private String username;
|
||||||
|
private String password;
|
||||||
|
private String firstname;
|
||||||
|
private String lastname;
|
||||||
|
@OneToOne
|
||||||
|
private Role role;
|
||||||
|
@OneToMany
|
||||||
|
private List<TimetrackAccount> timetrackAccounts;
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
package de.hft.geotime.user;
|
||||||
|
|
||||||
|
import org.springframework.data.repository.CrudRepository;
|
||||||
|
|
||||||
|
import javax.websocket.server.PathParam;
|
||||||
|
|
||||||
|
public interface TimetrackUserRepository extends CrudRepository<TimetrackUser, Long> {
|
||||||
|
|
||||||
|
TimetrackUser findFirstByUsername(@PathParam("username") String username);
|
||||||
|
|
||||||
|
}
|
@ -1,30 +0,0 @@
|
|||||||
package de.hft.geotime.user;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
import javax.persistence.Entity;
|
|
||||||
import javax.persistence.GeneratedValue;
|
|
||||||
import javax.persistence.GenerationType;
|
|
||||||
import javax.persistence.Id;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@Entity
|
|
||||||
public class User {
|
|
||||||
|
|
||||||
@Id
|
|
||||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
|
||||||
private long id;
|
|
||||||
private String username;
|
|
||||||
private String firstname;
|
|
||||||
private String lastname;
|
|
||||||
//TODO: roleid(FK)
|
|
||||||
//TODO: timetrackaccounts[List]
|
|
||||||
|
|
||||||
protected User() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public User(String firstname, String lastname) {
|
|
||||||
this.firstname = firstname;
|
|
||||||
this.lastname = lastname;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,35 @@
|
|||||||
|
package de.hft.geotime.user;
|
||||||
|
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/user")
|
||||||
|
public class UserController {
|
||||||
|
|
||||||
|
private TimetrackUserRepository userRepository;
|
||||||
|
private BCryptPasswordEncoder bCryptPasswordEncoder;
|
||||||
|
|
||||||
|
public UserController(TimetrackUserRepository userRepository, BCryptPasswordEncoder bCryptPasswordEncoder) {
|
||||||
|
this.userRepository = userRepository;
|
||||||
|
this.bCryptPasswordEncoder = bCryptPasswordEncoder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public String getUsername(Authentication authentication) {
|
||||||
|
TimetrackUser timetrackUser = userRepository.findFirstByUsername(authentication.getName());
|
||||||
|
return "Welcome back " + timetrackUser.getFirstname() + " " + timetrackUser.getLastname();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: implement register, maybe move to another class
|
||||||
|
@PostMapping("/sign-up")
|
||||||
|
public HashMap<String, Object> signUp(@RequestBody HashMap<String, Object> payload) {
|
||||||
|
return payload;
|
||||||
|
// user.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));
|
||||||
|
// userRepository.save(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
package de.hft.geotime.user;
|
||||||
|
|
||||||
|
import org.springframework.security.core.userdetails.User;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class UserDetailsServiceImpl implements UserDetailsService {
|
||||||
|
|
||||||
|
private final TimetrackUserRepository userRepository;
|
||||||
|
|
||||||
|
public UserDetailsServiceImpl(TimetrackUserRepository userRepository) {
|
||||||
|
this.userRepository = userRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||||
|
TimetrackUser timetrackUser = userRepository.findFirstByUsername(username);
|
||||||
|
if (timetrackUser == null) {
|
||||||
|
throw new UsernameNotFoundException(username);
|
||||||
|
}
|
||||||
|
System.out.println("Loaded user " + timetrackUser.getFirstname() + " " + timetrackUser.getLastname());
|
||||||
|
return new User(timetrackUser.getUsername(), timetrackUser.getPassword(), Collections.emptyList());
|
||||||
|
}
|
||||||
|
}
|
@ -1,14 +0,0 @@
|
|||||||
package de.hft.geotime.user;
|
|
||||||
|
|
||||||
import org.springframework.data.repository.PagingAndSortingRepository;
|
|
||||||
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
|
|
||||||
|
|
||||||
import javax.websocket.server.PathParam;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@RepositoryRestResource
|
|
||||||
public interface UserRepository extends PagingAndSortingRepository<User, Long> {
|
|
||||||
|
|
||||||
List<User> findByUsername(@PathParam("username") String username);
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,7 @@
|
|||||||
|
spring.datasource.url=jdbc:h2:mem:testdb
|
||||||
|
spring.datasource.driverClassName=org.h2.Driver
|
||||||
|
spring.datasource.username=sa
|
||||||
|
spring.datasource.password=
|
||||||
|
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
|
||||||
|
spring.h2.console.enabled=true
|
||||||
|
spring.h2.console.path=/h2-console
|
@ -2,4 +2,5 @@ spring.jpa.hibernate.ddl-auto=update
|
|||||||
spring.datasource.url=jdbc:mariadb://db:3306/geotime
|
spring.datasource.url=jdbc:mariadb://db:3306/geotime
|
||||||
spring.datasource.username=root
|
spring.datasource.username=root
|
||||||
spring.datasource.password=supersecure
|
spring.datasource.password=supersecure
|
||||||
|
spring.datasource.initialization-mode=always
|
||||||
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
|
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
|
@ -1,5 +1,5 @@
|
|||||||
server.port=80
|
server.port=80
|
||||||
spring.data.rest.basePath=/api
|
|
||||||
spring.datasource.hikari.initialization-fail-timeout=0
|
spring.datasource.hikari.initialization-fail-timeout=0
|
||||||
spring.datasource.hikari.max-lifetime=300000
|
spring.datasource.hikari.max-lifetime=300000
|
||||||
|
spring.jpa.show-sql=true
|
||||||
spring.profiles.active=prod
|
spring.profiles.active=prod
|
18
backend/src/main/resources/data.sql
Normal file
18
backend/src/main/resources/data.sql
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
SET FOREIGN_KEY_CHECKS=0;
|
||||||
|
|
||||||
|
DELETE FROM timetrack_user;
|
||||||
|
DELETE FROM role;
|
||||||
|
|
||||||
|
INSERT INTO role (id, `name`) VALUES
|
||||||
|
(1, 'Admin');
|
||||||
|
|
||||||
|
/* password is the firstname in lowercase e.g. marcel or tobias
|
||||||
|
https://bcrypt-generator.com/ with 10 rounds
|
||||||
|
*/
|
||||||
|
INSERT INTO timetrack_user (id, firstname, lastname, password, username, role_id) VALUES
|
||||||
|
(1, 'Marcel', 'Schwarz' ,'$2y$10$pDBv7dEaAiNs5Kr1.8g4XuTFx48zGxJu77rei4TlO.sDOF2yHWxo.', 'scma', 1),
|
||||||
|
(2, 'Tobias', 'Wieck' ,'$2y$10$Fxj5cGrZblGKjIExvS/MquEE0lgyYo1ILxPgPR2vSiaaLKkqJ.C.u', 'wito', 1),
|
||||||
|
(3, 'Tim', 'Zieger' ,'$2y$10$pYGHZhoaelceImO7aIN4nOkWJBp.oqNGFYaRAonHkYF4u9ljqPelC', 'ziti', 1),
|
||||||
|
(4, 'Simon', 'Kellner' ,'$2y$10$Puzm/Nr/Dyq3nQxlkXGIfubS5JPtXJSOf2e6mrQ6HhVYQN9YiQQsC', 'kesi', 1);
|
||||||
|
|
||||||
|
SET FOREIGN_KEY_CHECKS=1;
|
@ -1,5 +1,5 @@
|
|||||||
# build stage
|
# build stage
|
||||||
FROM node:12 as build-stage
|
FROM node:14 as build-stage
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
RUN npm install
|
RUN npm install
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<div>
|
<div>
|
||||||
<img alt="Vue logo" src="../../assets/logo.svg">
|
<u><router-link to="/"><img alt="Vue logo" src="../../assets/logo.svg"></router-link></u>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<u><router-link to="/login">Login</router-link></u>
|
<u><router-link to="/login">Login</router-link></u>
|
||||||
|
@ -5,6 +5,9 @@ import Profile from "../views/profile.vue";
|
|||||||
import missing from "../views/missing.vue";
|
import missing from "../views/missing.vue";
|
||||||
import Info from "../views/Info.vue";
|
import Info from "../views/Info.vue";
|
||||||
import TimeRecords from "../views/TimeRecords.vue";
|
import TimeRecords from "../views/TimeRecords.vue";
|
||||||
|
import About from "../views/About.vue";
|
||||||
|
import Login from "../views/Login.vue";
|
||||||
|
import Register from "../views/Register.vue";
|
||||||
Vue.use(VueRouter);
|
Vue.use(VueRouter);
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
@ -18,6 +21,16 @@ const routes = [
|
|||||||
name: "TimeRecords",
|
name: "TimeRecords",
|
||||||
component: TimeRecords
|
component: TimeRecords
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/login",
|
||||||
|
name: "Login",
|
||||||
|
component: Login
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/register",
|
||||||
|
name: "Register",
|
||||||
|
component: Register
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "/profile/:id",
|
path: "/profile/:id",
|
||||||
name: "Profile",
|
name: "Profile",
|
||||||
@ -38,11 +51,7 @@ const routes = [
|
|||||||
{
|
{
|
||||||
path: "/about",
|
path: "/about",
|
||||||
name: "About",
|
name: "About",
|
||||||
// route level code-splitting
|
component: About
|
||||||
// this generates a separate chunk (about.[hash].js) for this route
|
|
||||||
// which is lazy-loaded when the route is visited.
|
|
||||||
component: () =>
|
|
||||||
import(/* webpackChunkName: "about" */ "../views/About.vue")
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '*',
|
path: '*',
|
||||||
|
81
frontend/src/views/Login.vue
Normal file
81
frontend/src/views/Login.vue
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
<template>
|
||||||
|
<div class="login">
|
||||||
|
<form @submit="login">
|
||||||
|
<p>Username:</p>
|
||||||
|
<input type="text" name="username" placeholder="Username" v-model="username" required="required" oninvalid="this.setCustomValidity('Username cannot be empty.')" >
|
||||||
|
<p>Password:</p>
|
||||||
|
<input type="password" name="password" placeholder="Password" v-model="password" required="required" oninvalid="this.setCustomValidity('Password cannot be empty.')">
|
||||||
|
<br>
|
||||||
|
<input type="submit" value="Submit">
|
||||||
|
<p style="font-size: 10px">No account yet? <router-link to="/register"> Register</router-link></p>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "Login",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
username: '',
|
||||||
|
password: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
login(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.login{
|
||||||
|
color: #EBE7D9;
|
||||||
|
|
||||||
|
}
|
||||||
|
form {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-around;
|
||||||
|
}
|
||||||
|
input[type="text"], input[type=password] {
|
||||||
|
justify-content: center;
|
||||||
|
width: 40vw;
|
||||||
|
height: 20px;
|
||||||
|
background-color: #131313;
|
||||||
|
border: 2px solid #0096ff;
|
||||||
|
color: #EBE7D9;
|
||||||
|
font-family: 'Montserrat', sans-serif;
|
||||||
|
font-size: 12px;
|
||||||
|
box-sizing: content-box;
|
||||||
|
}
|
||||||
|
input[type="submit"] {
|
||||||
|
justify-content: center;
|
||||||
|
width: 40vw;
|
||||||
|
height: 20px;
|
||||||
|
border: 2px solid #0096ff ;
|
||||||
|
color: #EBE7D9;
|
||||||
|
background-color: #131313;
|
||||||
|
border-radius: 3px;
|
||||||
|
box-sizing: content-box;
|
||||||
|
padding: 0px;
|
||||||
|
font-family: 'Montserrat', sans-serif;
|
||||||
|
font-size: 12px;
|
||||||
|
}input[type="submit"]:hover {
|
||||||
|
justify-content: center;
|
||||||
|
width: 40vw;
|
||||||
|
height: 20px;
|
||||||
|
border: 2px solid #0096ff ;
|
||||||
|
color: #EBE7D9;
|
||||||
|
background-color: #272727;
|
||||||
|
border-radius: 3px;
|
||||||
|
box-sizing: content-box;
|
||||||
|
padding: 0px;
|
||||||
|
font-family: 'Montserrat', sans-serif;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
111
frontend/src/views/Register.vue
Normal file
111
frontend/src/views/Register.vue
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
<template>
|
||||||
|
<div class="register">
|
||||||
|
<form @submit="login">
|
||||||
|
<p>Firstname:</p>
|
||||||
|
<input type="text" name="firstname" placeholder="Firstname" v-model="firstname" required oninvalid="this.setCustomValidity('Firstname cannot be empty.')" oninput="this.setCustomValidity('')">
|
||||||
|
<p>Lastname:</p>
|
||||||
|
<input type="text" name="lastname" placeholder="Lastname" v-model="lastname" required oninvalid="this.setCustomValidity('Lastname cannot be empty.')" oninput="this.setCustomValidity('')">
|
||||||
|
<p>Username:</p>
|
||||||
|
<input type="text" name="username" placeholder="Username" v-model="username" required oninvalid="this.setCustomValidity('Username cannot be empty.')" oninput="this.setCustomValidity('')">
|
||||||
|
<p>Password:</p>
|
||||||
|
<input type="password" name="password" placeholder="Password" v-model="password" required oninvalid="this.setCustomValidity('Password cannot be empty.')" oninput="this.setCustomValidity('')">
|
||||||
|
<p>Confirm Password:</p>
|
||||||
|
<input type="password" name="passwordC" placeholder="Password" v-model="passwordC" required oninvalid="this.setCustomValidity('Password cannot be empty.')" oninput="this.setCustomValidity('')">
|
||||||
|
<br>
|
||||||
|
<input type="submit" value="Submit">
|
||||||
|
<p style="font-size: 10px">No account yet? <router-link to="/register"> Register</router-link></p>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "Register",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
firstname: '',
|
||||||
|
lastname: '',
|
||||||
|
username: '',
|
||||||
|
password: '',
|
||||||
|
passwordC: ''
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
login(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (this.password == this.passwordC) {
|
||||||
|
//register
|
||||||
|
}else {
|
||||||
|
alert("The password confirmation does not match the password");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// var password1 = document.getElementsByName('password');
|
||||||
|
// var password2 = document.getElementsByName('passwordC');
|
||||||
|
//
|
||||||
|
// var checkPasswordValidity = function() {
|
||||||
|
// if (password1.value != password2.value) {
|
||||||
|
// password2.setCustomValidity('Passwörter müssen übereinstimmen!');
|
||||||
|
// } else {
|
||||||
|
// password2.setCustomValidity('');
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// password1.addEventListener('change', checkPasswordValidity);
|
||||||
|
// password2.addEventListener('change', checkPasswordValidity);
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.register{
|
||||||
|
color: #EBE7D9;
|
||||||
|
font-size: 10px;
|
||||||
|
|
||||||
|
}
|
||||||
|
form {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-around;
|
||||||
|
}
|
||||||
|
input[type="text"], input[type=password] {
|
||||||
|
justify-content: center;
|
||||||
|
width: 40vw;
|
||||||
|
height: 20px;
|
||||||
|
background-color: #131313;
|
||||||
|
border: 2px solid #0096ff;
|
||||||
|
color: #EBE7D9;
|
||||||
|
font-family: 'Montserrat', sans-serif;
|
||||||
|
font-size: 12px;
|
||||||
|
box-sizing: content-box;
|
||||||
|
}
|
||||||
|
input[type="submit"] {
|
||||||
|
justify-content: center;
|
||||||
|
width: 40vw;
|
||||||
|
height: 20px;
|
||||||
|
border: 2px solid #0096ff ;
|
||||||
|
color: #EBE7D9;
|
||||||
|
background-color: #131313;
|
||||||
|
border-radius: 3px;
|
||||||
|
box-sizing: content-box;
|
||||||
|
padding: 0px;
|
||||||
|
font-family: 'Montserrat', sans-serif;
|
||||||
|
font-size: 12px;
|
||||||
|
}input[type="submit"]:hover {
|
||||||
|
justify-content: center;
|
||||||
|
width: 40vw;
|
||||||
|
height: 20px;
|
||||||
|
border: 2px solid #0096ff ;
|
||||||
|
color: #EBE7D9;
|
||||||
|
background-color: #272727;
|
||||||
|
border-radius: 3px;
|
||||||
|
box-sizing: content-box;
|
||||||
|
padding: 0px;
|
||||||
|
font-family: 'Montserrat', sans-serif;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
Loading…
Reference in New Issue
Block a user