weitere arbeiten an der UI
This commit is contained in:
38
.gitignore
vendored
38
.gitignore
vendored
@@ -1,6 +1,34 @@
|
||||
/target/
|
||||
.settings/
|
||||
.classpath
|
||||
.project
|
||||
*.class
|
||||
# Eclipse-spezifische Metadaten (WICHTIG)
|
||||
.metadata/
|
||||
.plugins/
|
||||
bin/
|
||||
tmp/
|
||||
*.tmp
|
||||
*.bak
|
||||
*.swp
|
||||
*~.nib
|
||||
local.properties
|
||||
.settings/
|
||||
.project
|
||||
.classpath
|
||||
.factorypath
|
||||
|
||||
# Maven (Spring Boot Standard)
|
||||
target/
|
||||
pom.xml.tag
|
||||
pom.xml.releaseBackup
|
||||
pom.xml.versionsBackup
|
||||
pom.xml.restoreBackup
|
||||
.releaseBackup
|
||||
release.properties
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
logs/
|
||||
|
||||
# Betriebssystem-Müll
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Private Konfigurationen (falls du Passwörter hier speicherst)
|
||||
application-local.properties
|
||||
|
||||
@@ -519,3 +519,24 @@ Command-line arguments: -os linux -ws gtk -arch x86_64 -product org.eclipse.epp
|
||||
!MESSAGE Warnings while parsing the commands from the 'org.eclipse.ui.commands' and 'org.eclipse.ui.actionDefinitions' extension points.
|
||||
!SUBENTRY 1 org.eclipse.ui 2 0 2026-03-01 19:35:42.071
|
||||
!MESSAGE Commands should really have a category: plug-in='org.springframework.tooling.boot.ls', id='spring.initializr.addStarters', categoryId='org.eclipse.lsp4e.commandCategory'
|
||||
|
||||
!ENTRY org.eclipse.jface 2 0 2026-03-01 19:57:46.028
|
||||
!MESSAGE Keybinding conflicts occurred. They may interfere with normal accelerator operation.
|
||||
!SUBENTRY 1 org.eclipse.jface 2 0 2026-03-01 19:57:46.028
|
||||
!MESSAGE A conflict occurred for CTRL+SHIFT+T:
|
||||
Binding(CTRL+SHIFT+T,
|
||||
ParameterizedCommand(Command(org.eclipse.jdt.ui.navigate.open.type,Open Type,
|
||||
Open a type in a Java editor,
|
||||
Category(org.eclipse.ui.category.navigate,Navigate,null,true),
|
||||
WorkbenchHandlerServiceHandler("org.eclipse.jdt.ui.navigate.open.type"),
|
||||
,,true),null),
|
||||
org.eclipse.ui.defaultAcceleratorConfiguration,
|
||||
org.eclipse.ui.contexts.window,,,system)
|
||||
Binding(CTRL+SHIFT+T,
|
||||
ParameterizedCommand(Command(org.eclipse.lsp4e.symbolInWorkspace,Go to Symbol in Workspace,
|
||||
,
|
||||
Category(org.eclipse.lsp4e.category,Language Servers,null,true),
|
||||
WorkbenchHandlerServiceHandler("org.eclipse.lsp4e.symbolInWorkspace"),
|
||||
,,true),null),
|
||||
org.eclipse.ui.defaultAcceleratorConfiguration,
|
||||
org.eclipse.ui.contexts.window,,,system)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
@@ -31,6 +31,10 @@ dependencies {
|
||||
testRuntimeOnly("com.h2database:h2")
|
||||
}
|
||||
|
||||
tasks.withType<JavaCompile> {
|
||||
options.compilerArgs.add("-parameters")
|
||||
}
|
||||
|
||||
tasks.withType<Test> {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@RestController
|
||||
@RestController("aufgabenSperreController")
|
||||
@RequestMapping("/sperre")
|
||||
@Transactional
|
||||
public class SperreController {
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
package de.oaa.xxx.config;
|
||||
|
||||
import io.jsonwebtoken.Claims;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.UUID;
|
||||
|
||||
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
|
||||
private final JwtService jwtService;
|
||||
|
||||
public JwtAuthenticationFilter(JwtService jwtService) {
|
||||
this.jwtService = jwtService;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||
throws ServletException, IOException {
|
||||
String token = request.getHeader("token");
|
||||
if (token != null && !token.isBlank()) {
|
||||
String clientIp = request.getRemoteAddr();
|
||||
Claims claims = jwtService.validateToken(token, clientIp);
|
||||
if (claims != null) {
|
||||
String userIdStr = claims.get("userId", String.class);
|
||||
UUID userId = UUID.fromString(userIdStr);
|
||||
UsernamePasswordAuthenticationToken authentication =
|
||||
new UsernamePasswordAuthenticationToken(userId, null, Collections.emptyList());
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
}
|
||||
}
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
package de.oaa.xxx.config;
|
||||
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Jws;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.Key;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PublicKey;
|
||||
import java.security.UnrecoverableKeyException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.util.Base64;
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
|
||||
@Service
|
||||
public class JwtService {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(JwtService.class);
|
||||
|
||||
@Value("${jwt.keystore.password}")
|
||||
private String keyPass;
|
||||
|
||||
@Value("${jwt.keystore.alias}")
|
||||
private String alias;
|
||||
|
||||
private KeyStore getKeystore() throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
|
||||
KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||
keystore.load(getClass().getClassLoader().getResourceAsStream("xxx.jks"), keyPass.toCharArray());
|
||||
return keystore;
|
||||
}
|
||||
|
||||
public String createToken(String email, UUID userId, String clientIp) {
|
||||
try {
|
||||
KeyStore keystore = getKeystore();
|
||||
Key key = keystore.getKey(alias, keyPass.toCharArray());
|
||||
String token = Jwts.builder()
|
||||
.issuer("OAA Games")
|
||||
.issuedAt(new Date())
|
||||
.claim("email", email)
|
||||
.claim("client", clientIp)
|
||||
.claim("userId", userId.toString())
|
||||
.signWith(key)
|
||||
.compact();
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Erstellter token: {}", token);
|
||||
}
|
||||
return token;
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Fehler beim Erstellen des Tokens", e);
|
||||
throw new RuntimeException("Token-Erstellung fehlgeschlagen", e);
|
||||
}
|
||||
}
|
||||
|
||||
public Claims validateToken(String token, String clientIp) {
|
||||
try {
|
||||
KeyStore keystore = getKeystore();
|
||||
Key key = keystore.getKey(alias, keyPass.toCharArray());
|
||||
Jws<Claims> claimsJws = Jwts.parser().verifyWith((java.security.PublicKey) keystore.getCertificate(alias).getPublicKey()).build().parseSignedClaims(token);
|
||||
Claims claims = claimsJws.getPayload();
|
||||
String tokenClient = claims.get("client", String.class);
|
||||
if (!clientIp.equals(tokenClient)) {
|
||||
LOGGER.warn("IP-Adresse stimmt nicht überein: Token={}, Request={}", tokenClient, clientIp);
|
||||
return null;
|
||||
}
|
||||
return claims;
|
||||
} catch (Exception e) {
|
||||
LOGGER.debug("Token-Validierung fehlgeschlagen: {}", e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public String getPublicKeyBase64() {
|
||||
try {
|
||||
KeyStore keystore = getKeystore();
|
||||
PublicKey publicKey = keystore.getCertificate(alias).getPublicKey();
|
||||
return Base64.getEncoder().encodeToString(publicKey.getEncoded());
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Fehler beim Laden des Public Keys", e);
|
||||
throw new RuntimeException("Public-Key-Laden fehlgeschlagen", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,16 +8,14 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe
|
||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class SecurityConfig {
|
||||
|
||||
private final JwtService jwtService;
|
||||
public SecurityConfig() {
|
||||
|
||||
public SecurityConfig(JwtService jwtService) {
|
||||
this.jwtService = jwtService;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -26,14 +24,23 @@ public class SecurityConfig {
|
||||
.csrf(AbstractHttpConfigurer::disable)
|
||||
.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
.requestMatchers(HttpMethod.GET, "/login", "/login/publickey").permitAll()
|
||||
.requestMatchers(HttpMethod.POST, "/user").permitAll()
|
||||
.requestMatchers(HttpMethod.POST, "/registration").permitAll()
|
||||
.requestMatchers(HttpMethod.GET, "/registration/activation/**").permitAll()
|
||||
.requestMatchers(HttpMethod.POST, "/filler").permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/error")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/*.html")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/css/**")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/js/**")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/images/**")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/favicon.ico")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher(HttpMethod.GET, "/login")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher(HttpMethod.GET, "/login/publickey")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher(HttpMethod.POST, "/user")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher(HttpMethod.GET, "/registration")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher(HttpMethod.POST, "/registration")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher(HttpMethod.GET, "/activation")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher(HttpMethod.GET, "/activation/**")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher(HttpMethod.POST, "/filler")).permitAll()
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.addFilterBefore(new JwtAuthenticationFilter(jwtService), UsernamePasswordAuthenticationFilter.class);
|
||||
);
|
||||
return http.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
package de.oaa.xxx.registration;
|
||||
|
||||
import de.oaa.xxx.user.Registration;
|
||||
import de.oaa.xxx.user.UserController;
|
||||
import java.net.URI;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.UUID;
|
||||
import de.oaa.xxx.user.Registration;
|
||||
import de.oaa.xxx.user.UserController;
|
||||
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/registration/activation")
|
||||
@RequestMapping("/activation")
|
||||
public class ActivationController {
|
||||
|
||||
private final RegistrationRepository registrationRepository;
|
||||
@@ -36,12 +38,13 @@ public class ActivationController {
|
||||
if (response.getStatusCode().is2xxSuccessful()) {
|
||||
registration.setActivated(Boolean.TRUE);
|
||||
registrationRepository.save(registration);
|
||||
return ResponseEntity.status(302).location(URI.create("/activation.html")).build();
|
||||
String redirect = "/login.html?email=" + java.net.URLEncoder.encode(registration.getEmail(), java.nio.charset.StandardCharsets.UTF_8);
|
||||
return ResponseEntity.status(302).location(URI.create(redirect)).build();
|
||||
} else {
|
||||
return ResponseEntity.internalServerError().build();
|
||||
}
|
||||
} else {
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +1,43 @@
|
||||
package de.oaa.xxx.registration;
|
||||
|
||||
import de.oaa.xxx.mail.Email;
|
||||
import de.oaa.xxx.mail.MailService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import de.oaa.xxx.mail.Email;
|
||||
import de.oaa.xxx.mail.MailService;
|
||||
import de.oaa.xxx.user.UserRepository;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/registration")
|
||||
public class RegistrationController {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(RegistrationController.class);
|
||||
|
||||
@Value("${app.base-url:http://localhost:8080}")
|
||||
private String baseUrl;
|
||||
|
||||
private final RegistrationRepository registrationRepository;
|
||||
private final UserRepository userRepository;
|
||||
private final MailService mailService;
|
||||
|
||||
public RegistrationController(RegistrationRepository registrationRepository, MailService mailService) {
|
||||
public RegistrationController(RegistrationRepository registrationRepository, UserRepository userRepository,
|
||||
MailService mailService) {
|
||||
this.registrationRepository = registrationRepository;
|
||||
this.userRepository = userRepository;
|
||||
this.mailService = mailService;
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<Void> create(@RequestBody Registration registration) {
|
||||
public ResponseEntity<String> create(@RequestBody Registration registration) {
|
||||
LOGGER.info("POST {}: {}", getClass().getName(), registration);
|
||||
if (!registrationRepository.findByEmail(registration.getEmail()).isEmpty()) {
|
||||
if (registrationRepository.findByEmail(registration.getEmail()).isPresent()
|
||||
|| userRepository.findByEmail(registration.getEmail()).isPresent()) {
|
||||
LOGGER.warn("User mit E-Mail {} bereits vorhanden", registration.getEmail());
|
||||
return ResponseEntity.badRequest().build();
|
||||
}
|
||||
@@ -44,16 +54,23 @@ public class RegistrationController {
|
||||
registrationRepository.delete(entity);
|
||||
return ResponseEntity.internalServerError().build();
|
||||
}
|
||||
return ResponseEntity.accepted().build();
|
||||
return ResponseEntity.status(202).build();
|
||||
}
|
||||
|
||||
private String getMailText(Registration registration) {
|
||||
String uuid = registration.getId().toString();
|
||||
return """
|
||||
<html>
|
||||
<h1>Moin %s</h1>
|
||||
<p>Vielen Dank für deine Anmeldung bei XXX-BDSM Game!</p>
|
||||
<p>Klicke <a href="http://localhost:8080/registration/activation/%s">hier</a> um deine E-Mail-Adresse zu bestätigen.</p>
|
||||
<p>Und nun viel Spass beim Spiel!</p>
|
||||
</html>""".formatted(registration.getName(), registration.getId().toString());
|
||||
<p>Klicke <a href="%s/activation/%s">hier</a> um deine E-Mail-Adresse zu bestätigen.</p>
|
||||
<hr style="margin:1.5rem 0; border:none; border-top:1px solid #ddd;">
|
||||
<p style="color:#666; font-size:0.9em;">
|
||||
Falls der Link nicht funktioniert, kannst du deine E-Mail-Adresse auch manuell bestätigen.<br>
|
||||
Öffne dazu <a href="%s/activate.html">%s/activate.html</a> und gib folgenden Code ein:
|
||||
</p>
|
||||
<p style="font-family:monospace; font-size:1.1em; background:#f4f4f4; padding:0.5rem 1rem; border-radius:4px; display:inline-block;">%s</p>
|
||||
<p>Viel Spaß beim Spiel!</p>
|
||||
</html>""".formatted(registration.getName(), baseUrl, uuid, baseUrl, baseUrl, uuid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@ package de.oaa.xxx.registration;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface RegistrationRepository extends JpaRepository<RegistrationEntity, UUID> {
|
||||
|
||||
List<RegistrationEntity> findByEmail(String email);
|
||||
Optional<RegistrationEntity> findByEmail(String email);
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@RestController
|
||||
@RestController("sessionSperreController")
|
||||
@RequestMapping("/session/sperre")
|
||||
@Transactional
|
||||
public class SperreController {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
package de.oaa.xxx.user;
|
||||
|
||||
import de.oaa.xxx.config.JwtService;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@@ -11,8 +12,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/login")
|
||||
@@ -21,21 +21,18 @@ public class LoginController {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(LoginController.class);
|
||||
|
||||
private final UserRepository userRepository;
|
||||
private final JwtService jwtService;
|
||||
|
||||
public LoginController(UserRepository userRepository, JwtService jwtService) {
|
||||
public LoginController(UserRepository userRepository) {
|
||||
this.userRepository = userRepository;
|
||||
this.jwtService = jwtService;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public ResponseEntity<String> login(@RequestParam String email, @RequestParam String hash,
|
||||
HttpServletRequest req) {
|
||||
public ResponseEntity<User> login(@RequestParam String email, @RequestParam String hash,
|
||||
HttpServletRequest req) {
|
||||
Optional<UserEntity> user = userRepository.findByEmailAndPassword(email, hash);
|
||||
if (user.isPresent()) {
|
||||
LOGGER.info("User erfolgreich angemeldet: {}", email);
|
||||
String token = jwtService.createToken(email, user.get().getUserId(), req.getRemoteAddr());
|
||||
return ResponseEntity.ok(token);
|
||||
return ResponseEntity.ok(user.get().toUser());
|
||||
} else {
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
@@ -47,9 +44,4 @@ public class LoginController {
|
||||
.map(entity -> ResponseEntity.ok(entity.toUser()))
|
||||
.orElse(ResponseEntity.noContent().build());
|
||||
}
|
||||
|
||||
@GetMapping("/publickey")
|
||||
public ResponseEntity<String> getPublicKey() {
|
||||
return ResponseEntity.ok(jwtService.getPublicKeyBase64());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,10 +11,18 @@ spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect
|
||||
spring.jpa.properties.hibernate.type.preferred_uuid_jdbc_type=VARCHAR
|
||||
|
||||
# Mail
|
||||
spring.mail.host=${MAIL_HOST:localhost}
|
||||
spring.mail.port=${MAIL_PORT:25}
|
||||
spring.mail.username=${MAIL_USER:}
|
||||
spring.mail.password=${MAIL_PASSWORD:}
|
||||
#spring.mail.host=${MAIL_HOST:localhost}
|
||||
#spring.mail.port=${MAIL_PORT:25}
|
||||
#spring.mail.username=${MAIL_USER:}
|
||||
#spring.mail.password=${MAIL_PASSWORD:}
|
||||
#spring.mail.properties.mail.smtp.auth=false
|
||||
#spring.mail.properties.mail.smtp.starttls.enable=false
|
||||
|
||||
# Mailpit
|
||||
spring.mail.host=localhost
|
||||
spring.mail.port=1025
|
||||
spring.mail.username=
|
||||
spring.mail.password=
|
||||
spring.mail.properties.mail.smtp.auth=false
|
||||
spring.mail.properties.mail.smtp.starttls.enable=false
|
||||
|
||||
@@ -25,3 +33,4 @@ jwt.keystore.alias=xxx
|
||||
|
||||
# Server
|
||||
server.port=8080
|
||||
server.servlet.context-path=/
|
||||
|
||||
92
xxxthegame/src/main/resources/static/activate.html
Normal file
92
xxxthegame/src/main/resources/static/activate.html
Normal file
@@ -0,0 +1,92 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>XXX The Game – Aktivierung</title>
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<h1>XXX The Game</h1>
|
||||
<p class="subtitle">E-Mail-Adresse bestätigen</p>
|
||||
|
||||
<p style="font-size:0.9rem; color:#aaa; margin-top:0.5rem; line-height:1.5;">
|
||||
Du hast eine E-Mail mit einem Aktivierungslink erhalten.<br>
|
||||
Falls der Link nicht funktioniert, gib hier deinen Aktivierungscode ein.
|
||||
</p>
|
||||
|
||||
<label for="uuid" style="margin-top:1.5rem;">Aktivierungscode</label>
|
||||
<input type="text" id="uuid" placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" autocomplete="off" />
|
||||
|
||||
<button class="full-width" id="activateBtn" onclick="activate()">Jetzt aktivieren</button>
|
||||
|
||||
<div class="message" id="message"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const email = params.get('email');
|
||||
if (email) {
|
||||
showMessage(`Eine Bestätigungs-E-Mail wurde an ${email} gesendet.`, 'success');
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', e => {
|
||||
if (e.key === 'Enter') activate();
|
||||
});
|
||||
|
||||
async function activate() {
|
||||
const uuid = document.getElementById('uuid').value.trim();
|
||||
const btn = document.getElementById('activateBtn');
|
||||
|
||||
if (!uuid) {
|
||||
showMessage('Bitte den Aktivierungscode eingeben.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Wird aktiviert…';
|
||||
hideMessage();
|
||||
|
||||
try {
|
||||
const response = await fetch(`/activation/${encodeURIComponent(uuid)}`, {
|
||||
method: 'GET',
|
||||
redirect: 'follow'
|
||||
});
|
||||
|
||||
if (response.redirected) {
|
||||
window.location.href = response.url;
|
||||
} else if (response.ok) {
|
||||
window.location.href = '/login.html';
|
||||
} else if (response.status === 204) {
|
||||
showMessage('Code ungültig oder bereits verwendet.', 'error');
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Jetzt aktivieren';
|
||||
} else {
|
||||
showMessage(`Fehler: HTTP ${response.status}`, 'error');
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Jetzt aktivieren';
|
||||
}
|
||||
} catch (err) {
|
||||
showMessage('Server nicht erreichbar.', 'error');
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Jetzt aktivieren';
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
function showMessage(text, type) {
|
||||
const el = document.getElementById('message');
|
||||
el.textContent = text;
|
||||
el.className = `message ${type}`;
|
||||
el.style.display = 'block';
|
||||
}
|
||||
|
||||
function hideMessage() {
|
||||
document.getElementById('message').style.display = 'none';
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
163
xxxthegame/src/main/resources/static/css/style.css
Normal file
163
xxxthegame/src/main/resources/static/css/style.css
Normal file
@@ -0,0 +1,163 @@
|
||||
/* ── Reset ── */
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* ── Base ── */
|
||||
body {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #1a1a2e;
|
||||
font-family: 'Segoe UI', sans-serif;
|
||||
color: #eee;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #e94560;
|
||||
}
|
||||
|
||||
p {
|
||||
color: #888;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
/* ── Card ── */
|
||||
.card {
|
||||
background: #16213e;
|
||||
border: 1px solid #0f3460;
|
||||
border-radius: 12px;
|
||||
padding: 2.5rem;
|
||||
width: 100%;
|
||||
max-width: 380px;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.card h1 {
|
||||
text-align: center;
|
||||
font-size: 1.6rem;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
text-align: center;
|
||||
font-size: 0.85rem;
|
||||
color: #888;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
/* ── Form elements ── */
|
||||
label {
|
||||
display: block;
|
||||
font-size: 0.8rem;
|
||||
color: #aaa;
|
||||
margin-bottom: 0.3rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
padding: 0.65rem 0.9rem;
|
||||
border: 1px solid #0f3460;
|
||||
border-radius: 6px;
|
||||
background: #0f3460;
|
||||
color: #eee;
|
||||
font-size: 1rem;
|
||||
outline: none;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
|
||||
input:focus {
|
||||
border-color: #e94560;
|
||||
}
|
||||
|
||||
/* ── Buttons ── */
|
||||
button, .btn {
|
||||
display: inline-block;
|
||||
padding: 0.75rem 2.5rem;
|
||||
background: #e94560;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
button:hover:not(:disabled), .btn:hover {
|
||||
background: #c73652;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
button.full-width {
|
||||
width: 100%;
|
||||
margin-top: 1.75rem;
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
button.secondary {
|
||||
background: #0f3460;
|
||||
font-weight: normal;
|
||||
padding: 0.3rem 0.7rem;
|
||||
font-size: 0.75rem;
|
||||
width: auto;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
button.secondary:hover {
|
||||
background: #1a4a8a;
|
||||
}
|
||||
|
||||
/* ── Messages ── */
|
||||
.message {
|
||||
margin-top: 1.25rem;
|
||||
padding: 0.65rem 0.9rem;
|
||||
border-radius: 6px;
|
||||
font-size: 0.85rem;
|
||||
display: none;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.message.error {
|
||||
background: #3d0f1a;
|
||||
border: 1px solid #e94560;
|
||||
color: #e94560;
|
||||
}
|
||||
|
||||
.message.success {
|
||||
background: #0f3d1a;
|
||||
border: 1px solid #2ecc71;
|
||||
color: #2ecc71;
|
||||
}
|
||||
|
||||
/* ── Token box ── */
|
||||
.token-box {
|
||||
margin-top: 1.25rem;
|
||||
padding: 0.65rem 0.9rem;
|
||||
background: #0f1e3d;
|
||||
border: 1px solid #0f3460;
|
||||
border-radius: 6px;
|
||||
font-size: 0.75rem;
|
||||
color: #aaa;
|
||||
word-break: break-all;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.token-box span {
|
||||
display: block;
|
||||
font-size: 0.7rem;
|
||||
color: #666;
|
||||
margin-bottom: 0.4rem;
|
||||
}
|
||||
48
xxxthegame/src/main/resources/static/index.html
Normal file
48
xxxthegame/src/main/resources/static/index.html
Normal file
@@ -0,0 +1,48 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>XXX The Game</title>
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<h1>XXX The Game</h1>
|
||||
<p>Das Erwachsenenspiel für Paare und Gruppen</p>
|
||||
<div style="display:flex; gap:1rem;">
|
||||
<a class="btn" href="/login.html">Anmelden</a>
|
||||
<a class="btn" href="/registration.html" style="background:#0f3460;">Registrieren</a>
|
||||
</div>
|
||||
|
||||
<div id="fillerMsg" class="message" style="max-width:320px;"></div>
|
||||
|
||||
<button onclick="fillDb()" style="background:none; border:1px solid #333; color:#555; font-size:0.75rem; padding:0.3rem 0.8rem; border-radius:4px; margin-top:-1rem;">
|
||||
DB befüllen
|
||||
</button>
|
||||
|
||||
<script>
|
||||
async function fillDb() {
|
||||
const btn = event.currentTarget;
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Wird befüllt…';
|
||||
try {
|
||||
const response = await fetch('/filler', { method: 'POST' });
|
||||
const msg = document.getElementById('fillerMsg');
|
||||
if (response.ok) {
|
||||
msg.textContent = 'Datenbank erfolgreich befüllt.';
|
||||
msg.className = 'message success';
|
||||
} else {
|
||||
msg.textContent = `Fehler: HTTP ${response.status}`;
|
||||
msg.className = 'message error';
|
||||
}
|
||||
msg.style.display = 'block';
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'DB befüllen';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -4,147 +4,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>XXX The Game – Login</title>
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #1a1a2e;
|
||||
font-family: 'Segoe UI', sans-serif;
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: #16213e;
|
||||
border: 1px solid #0f3460;
|
||||
border-radius: 12px;
|
||||
padding: 2.5rem;
|
||||
width: 100%;
|
||||
max-width: 380px;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
font-size: 1.6rem;
|
||||
margin-bottom: 0.25rem;
|
||||
color: #e94560;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
text-align: center;
|
||||
font-size: 0.85rem;
|
||||
color: #888;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
font-size: 0.8rem;
|
||||
color: #aaa;
|
||||
margin-bottom: 0.3rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
padding: 0.65rem 0.9rem;
|
||||
border: 1px solid #0f3460;
|
||||
border-radius: 6px;
|
||||
background: #0f3460;
|
||||
color: #eee;
|
||||
font-size: 1rem;
|
||||
outline: none;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
|
||||
input:focus {
|
||||
border-color: #e94560;
|
||||
}
|
||||
|
||||
button {
|
||||
margin-top: 1.75rem;
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
background: #e94560;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
button:hover:not(:disabled) {
|
||||
background: #c73652;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.message {
|
||||
margin-top: 1.25rem;
|
||||
padding: 0.65rem 0.9rem;
|
||||
border-radius: 6px;
|
||||
font-size: 0.85rem;
|
||||
display: none;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.message.error {
|
||||
background: #3d0f1a;
|
||||
border: 1px solid #e94560;
|
||||
color: #e94560;
|
||||
}
|
||||
|
||||
.message.success {
|
||||
background: #0f3d1a;
|
||||
border: 1px solid #2ecc71;
|
||||
color: #2ecc71;
|
||||
}
|
||||
|
||||
.token-box {
|
||||
margin-top: 1.25rem;
|
||||
padding: 0.65rem 0.9rem;
|
||||
background: #0f1e3d;
|
||||
border: 1px solid #0f3460;
|
||||
border-radius: 6px;
|
||||
font-size: 0.75rem;
|
||||
color: #aaa;
|
||||
word-break: break-all;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.token-box span {
|
||||
display: block;
|
||||
font-size: 0.7rem;
|
||||
color: #666;
|
||||
margin-bottom: 0.4rem;
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
margin-top: 0.5rem;
|
||||
width: auto;
|
||||
padding: 0.3rem 0.7rem;
|
||||
font-size: 0.75rem;
|
||||
background: #0f3460;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.copy-btn:hover {
|
||||
background: #1a4a8a;
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
@@ -157,19 +17,21 @@
|
||||
<label for="password">Passwort</label>
|
||||
<input type="password" id="password" placeholder="••••••••" autocomplete="current-password" />
|
||||
|
||||
<button id="loginBtn" onclick="login()">Anmelden</button>
|
||||
<button class="full-width" id="loginBtn" onclick="login()">Anmelden</button>
|
||||
|
||||
<div class="message" id="message"></div>
|
||||
|
||||
<div class="token-box" id="tokenBox">
|
||||
<span>JWT Token</span>
|
||||
<div id="tokenText"></div>
|
||||
<button class="copy-btn" onclick="copyToken()">Kopieren</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let currentToken = null;
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const email = params.get('email');
|
||||
if (email) {
|
||||
document.getElementById('email').value = email;
|
||||
document.getElementById('password').focus();
|
||||
showMessage('E-Mail-Adresse bestätigt! Du kannst dich jetzt anmelden.', 'success');
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', e => {
|
||||
if (e.key === 'Enter') login();
|
||||
@@ -196,28 +58,30 @@
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Wird geprüft…';
|
||||
hideMessage();
|
||||
hideToken();
|
||||
|
||||
try {
|
||||
const hash = await sha256(password);
|
||||
const url = `/login?email=${encodeURIComponent(email)}&hash=${encodeURIComponent(hash)}`;
|
||||
const response = await fetch(url, { method: 'GET' });
|
||||
|
||||
if (response.ok) {
|
||||
currentToken = await response.text();
|
||||
showMessage('Login erfolgreich!', 'success');
|
||||
showToken(currentToken);
|
||||
if (response.status === 200) {
|
||||
const user = await response.json();
|
||||
sessionStorage.setItem('user', JSON.stringify(user));
|
||||
window.location.href = '/userhome.html';
|
||||
} else if (response.status === 204) {
|
||||
showMessage('E-Mail oder Passwort falsch.', 'error');
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Anmelden';
|
||||
} else {
|
||||
showMessage(`Fehler: HTTP ${response.status}`, 'error');
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Anmelden';
|
||||
}
|
||||
} catch (err) {
|
||||
showMessage('Server nicht erreichbar.', 'error');
|
||||
console.error(err);
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Anmelden';
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,26 +95,6 @@
|
||||
function hideMessage() {
|
||||
document.getElementById('message').style.display = 'none';
|
||||
}
|
||||
|
||||
function showToken(token) {
|
||||
document.getElementById('tokenText').textContent = token;
|
||||
document.getElementById('tokenBox').style.display = 'block';
|
||||
}
|
||||
|
||||
function hideToken() {
|
||||
document.getElementById('tokenBox').style.display = 'none';
|
||||
currentToken = null;
|
||||
}
|
||||
|
||||
function copyToken() {
|
||||
if (currentToken) {
|
||||
navigator.clipboard.writeText(currentToken).then(() => {
|
||||
const btn = document.querySelector('.copy-btn');
|
||||
btn.textContent = 'Kopiert!';
|
||||
setTimeout(() => btn.textContent = 'Kopieren', 1500);
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
111
xxxthegame/src/main/resources/static/registration.html
Normal file
111
xxxthegame/src/main/resources/static/registration.html
Normal file
@@ -0,0 +1,111 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>XXX The Game – Registrierung</title>
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<h1>XXX The Game</h1>
|
||||
<p class="subtitle">Neues Konto erstellen</p>
|
||||
|
||||
<label for="name">Name</label>
|
||||
<input type="text" id="name" placeholder="Dein Name" autocomplete="name" />
|
||||
|
||||
<label for="email">E-Mail</label>
|
||||
<input type="email" id="email" placeholder="deine@email.de" autocomplete="email" />
|
||||
|
||||
<label for="password">Passwort</label>
|
||||
<input type="password" id="password" placeholder="••••••••" autocomplete="new-password" />
|
||||
|
||||
<label for="passwordConfirm">Passwort wiederholen</label>
|
||||
<input type="password" id="passwordConfirm" placeholder="••••••••" autocomplete="new-password" />
|
||||
|
||||
<button class="full-width" id="registerBtn" onclick="register()">Registrieren</button>
|
||||
|
||||
<div class="message" id="message"></div>
|
||||
|
||||
<p style="text-align:center; margin-top:1.5rem; font-size:0.85rem;">
|
||||
Bereits registriert? <a href="/login.html" style="color:#e94560;">Anmelden</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('keydown', e => {
|
||||
if (e.key === 'Enter') register();
|
||||
});
|
||||
|
||||
async function sha256(text) {
|
||||
const encoder = new TextEncoder();
|
||||
const data = encoder.encode(text);
|
||||
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
|
||||
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
||||
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
||||
}
|
||||
|
||||
async function register() {
|
||||
const name = document.getElementById('name').value.trim();
|
||||
const email = document.getElementById('email').value.trim();
|
||||
const password = document.getElementById('password').value;
|
||||
const passwordConfirm = document.getElementById('passwordConfirm').value;
|
||||
const btn = document.getElementById('registerBtn');
|
||||
|
||||
if (!name || !email || !password || !passwordConfirm) {
|
||||
showMessage('Bitte alle Felder ausfüllen.', 'error');
|
||||
return;
|
||||
}
|
||||
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
||||
showMessage('Bitte eine gültige E-Mail-Adresse eingeben.', 'error');
|
||||
return;
|
||||
}
|
||||
if (password !== passwordConfirm) {
|
||||
showMessage('Die Passwörter stimmen nicht überein.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Wird verarbeitet…';
|
||||
hideMessage();
|
||||
|
||||
try {
|
||||
const passwordHash = await sha256(password);
|
||||
const response = await fetch('/registration', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name, email, passwordHash })
|
||||
});
|
||||
|
||||
if (response.status === 202) {
|
||||
window.location.href = `/activate.html?email=${encodeURIComponent(email)}`;
|
||||
} else if (response.status === 400) {
|
||||
showMessage('Diese E-Mail-Adresse ist bereits registriert.', 'error');
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Registrieren';
|
||||
} else {
|
||||
showMessage(`Fehler: HTTP ${response.status}`, 'error');
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Registrieren';
|
||||
}
|
||||
} catch (err) {
|
||||
showMessage('Server nicht erreichbar.', 'error');
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Registrieren';
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
function showMessage(text, type) {
|
||||
const el = document.getElementById('message');
|
||||
el.textContent = text;
|
||||
el.className = `message ${type}`;
|
||||
el.style.display = 'block';
|
||||
}
|
||||
|
||||
function hideMessage() {
|
||||
document.getElementById('message').style.display = 'none';
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
23
xxxthegame/src/main/resources/static/userhome.html
Normal file
23
xxxthegame/src/main/resources/static/userhome.html
Normal file
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>XXX The Game</title>
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<h1>XXX The Game</h1>
|
||||
<p id="greeting"></p>
|
||||
|
||||
<script>
|
||||
const userJson = sessionStorage.getItem('user');
|
||||
if (!userJson) {
|
||||
window.location.href = '/login.html';
|
||||
} else {
|
||||
const user = JSON.parse(userJson);
|
||||
document.getElementById('greeting').textContent = 'Willkommen, ' + user.name + '!';
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user