Build Prototype for Login

This commit is contained in:
CodeSteak 2020-04-08 18:27:46 +02:00
parent 26b64b8ab4
commit 7c82b7b547
19 changed files with 213 additions and 344 deletions

View File

@ -23,6 +23,9 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.security:spring-security-core'
// implementation 'org.springframework.session:spring-session-jdbc'
implementation 'com.github.gwenn:sqlite-dialect:0.1.0'
implementation 'org.springframework.boot:spring-boot-devtools'
implementation 'org.xerial:sqlite-jdbc:3.28.0'
testCompile("org.springframework.boot:spring-boot-starter-test")

View File

@ -0,0 +1,9 @@
/* password is 123 */
INSERT INTO users ("created", "email", "password_hash", "gets_ads", "is_active", "is_employee", "isb2b")
VALUES (datetime('now','localtime') ||'.0', "emp@ecom", "$2a$10$zFiqcePBmXHErD86vkI.vO1dnX20ezoVSM8xjGi59nktXYQv0o.fK", "0", "1", "1", "0");
INSERT INTO users ("created", "email", "password_hash", "gets_ads", "is_active", "is_employee", "isb2b")
VALUES (datetime('now','localtime') ||'.0', "user@ecom", "$2a$10$zFiqcePBmXHErD86vkI.vO1dnX20ezoVSM8xjGi59nktXYQv0o.fK", "1", "1", "0", "0");
INSERT INTO users ("created", "email", "password_hash", "gets_ads", "is_active", "is_employee", "isb2b")
VALUES (datetime('now','localtime') ||'.0', "blocked@ecom", "$2a$10$zFiqcePBmXHErD86vkI.vO1dnX20ezoVSM8xjGi59nktXYQv0o.fK", "1", "0", "0", "0");

View File

@ -9,7 +9,7 @@ import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
@SpringBootApplication
@ComponentScan(basePackages = {"org.hso.ecommerce"})
@EntityScan("org.hso.ecommerce")
@EnableJpaRepositories
@EnableJpaRepositories("org.hso.ecommerce")
public class Application {
public static void main(String[] args){

View File

@ -1,6 +1,8 @@
package org.hso.ecommerce.app;
import org.hso.ecommerce.components.InfoDemoInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@ -12,9 +14,16 @@ import org.hso.ecommerce.components.ErrorDemoInterceptor;
@Configuration
public class Config implements WebMvcConfigurer {
// Copied from https://stackoverflow.com/questions/18218386/cannot-autowire-service-in-handlerinterceptoradapter/18218439
// Currently not needed for the other interceptors. They will be removed anyway.
@Bean
public LoginIntercepter buildLoginIntercepter() {
return new LoginIntercepter();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginIntercepter());
registry.addInterceptor(buildLoginIntercepter());
registry.addInterceptor(new ErrorDemoInterceptor());
registry.addInterceptor(new InfoDemoInterceptor());
}

View File

@ -1,17 +1,15 @@
package org.hso.ecommerce.app;
import org.hso.ecommerce.contoller.Login;
import org.hso.ecommerce.db.CustomerRepository;
import org.hso.ecommerce.entities.Customer;
import org.hso.ecommerce.db.repos.UserRepository;
import org.hso.ecommerce.entities.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.UUID;
import javax.servlet.http.HttpSession;
import java.util.Optional;
/**
* TODO clean up this class
@ -19,7 +17,10 @@ import java.util.UUID;
@Controller
public class RequestController {
static int notSoRandom = 0;
@Autowired
private final UserRepository userRepository = null;
static int notSoRandom = 0;
@GetMapping("/")
public String home() {
@ -32,10 +33,31 @@ public class RequestController {
}
@PostMapping("/login")
public String loginPost(HttpServletResponse response, @RequestParam(value = "goto", required = false) String gto) {
response.addCookie(new Cookie("login", "true"));
public String loginPost(
HttpServletRequest request,
HttpServletResponse response,
@RequestParam("username") String username,
@RequestParam("password") String password,
HttpSession session
) {
String gto = (String) session.getAttribute("afterLogin");
if (gto != null && gto.startsWith("/")) {
Optional<User> user = userRepository.findByEmail(username);
if(user.isEmpty()) {
request.setAttribute("error", "Email Adresse falsch.");
response.setStatus(HttpServletResponse.SC_EXPECTATION_FAILED);
return "login";
}
if (!user.get().validatePassword(password)) {
request.setAttribute("error", "Passwort falsch.");
response.setStatus(HttpServletResponse.SC_EXPECTATION_FAILED);
return "login";
}
session.setAttribute("userId", user.get().getId());
if (gto != null && gto.startsWith("/")) {
return "redirect:" + gto;
} else {
return "redirect:/";
@ -43,8 +65,10 @@ public class RequestController {
}
@PostMapping("/logout")
public String logoutPost(HttpServletResponse response) {
response.addCookie(new Cookie("login", "false"));
public String logoutPost(HttpServletResponse response,
HttpSession session
) {
session.removeAttribute("userId");
return "redirect:/";
}
@ -53,6 +77,17 @@ public class RequestController {
return "register";
}
@PostMapping("/register")
public String registerPost(
@RequestParam("username") String username,
@RequestParam("password") String password,
@RequestParam("password2") String password2,
@RequestParam("type") String type
) {
return "redirect:/";
}
@GetMapping("/shop/")
public String shop() {
return "shop/index";
@ -64,7 +99,8 @@ public class RequestController {
}
@GetMapping("/shop/checkout")
public String shopCheckout() {
public String shopCheckout(HttpSession session, HttpServletRequest request) {
session.setAttribute("afterLogin", request.getRequestURI());
return "shop/checkout";
}
@ -72,6 +108,7 @@ public class RequestController {
public String shopCheckoutFinish() {
return "shop/checkoutFinish";
}
@GetMapping("/shop/checkoutFinish")
public String shopCheckoutFinishGET() {
return "shop/checkoutFinish";
@ -83,15 +120,20 @@ public class RequestController {
}
@PostMapping("/shop/articles/{id}")
public String shopArticlesByIdBuy(@RequestAttribute("customer") Boolean isCustomer, @PathVariable("id") Integer id, @RequestParam("fastcheckout") Boolean fastcheckout ) {
if (isCustomer) {
public String shopArticlesByIdBuy(HttpSession session,
@RequestAttribute(value = "user", required = false) User customer,
@PathVariable("id") Integer id,
@RequestParam("fastcheckout") Boolean fastcheckout
) {
if (customer != null) {
if (!fastcheckout) {
return "shop/articles/post_add";
} else {
return "shop/checkout";
}
} else {
return "redirect:/login?goto=%2Fshop%2Farticles%2F"+id;
session.setAttribute("afterLogin", "/shop/articles/"+id);
return "redirect:/login";
}
}

View File

@ -1,6 +1,8 @@
package org.hso.ecommerce.app;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@ -18,7 +20,6 @@ public class UserRequestController {
return "user/settings";
}
@GetMapping("/orders/")
public String userOrdeers() { return "user/orders/index"; }
@ -31,5 +32,5 @@ public class UserRequestController {
public String userNotifications() {
return "user/notifications/index";
}
}

View File

@ -1,33 +1,63 @@
package org.hso.ecommerce.components;
import org.springframework.boot.web.servlet.server.Session;
import org.hibernate.Session;
import org.hso.ecommerce.db.repos.UserRepository;
import org.hso.ecommerce.entities.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.Model;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.Optional;
@Component
public class LoginIntercepter implements HandlerInterceptor {
@Autowired
private final UserRepository userRepository = null;
@Override
public boolean preHandle(
HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie c : cookies) {
if (c.getName().equals("login")) {
request.setAttribute("customer", c.getValue().equals("true"));
return true;
}
HttpSession session = request.getSession();
Object userId = session.getAttribute("userId");
if(request.getRequestURI().startsWith("/user/")) {
System.out.println("USER");
if(userId == null) {
session.setAttribute("afterLogin", request.getRequestURI());
response.sendRedirect("/login");
return false;
}
}
request.setAttribute("customer", false);
if(request.getRequestURI().startsWith("/intern/")) {
System.out.println("intern");
if(userId == null) {
session.setAttribute("afterLogin", request.getRequestURI());
response.sendRedirect("/login");
return false;
}
}
if(userId != null) {
Optional<User> user = userRepository.findById((Long) userId);
user.ifPresent(value -> request.setAttribute("user", value));
}
return true;
}
@Override
public void postHandle(
HttpServletRequest request, HttpServletResponse response, Object handler,

View File

@ -1,42 +0,0 @@
package org.hso.ecommerce.contoller;
import org.hso.ecommerce.db.CustomerRepository;
import org.hso.ecommerce.entities.Customer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import javax.servlet.http.Cookie;
import java.util.List;
import java.util.UUID;
public class Login {
private final CustomerRepository customerRepo;
@Autowired
public Login(CustomerRepository customerRepo) {
this.customerRepo = customerRepo;
}
public Cookie getLoginToken(Customer customer) {
// do the login magic and get a loginToken
System.out.println(customer.username);
System.out.println(customer.password);
List<Customer> customers = customerRepo.findByUsername(customer.username);
if (customers.size() == 1 && (customers.get(0).username.equals(customer.username) && customers.get(0).password.equals(customer.password))) {
System.out.println("The login data is valid");
String loginToken = UUID.randomUUID().toString();
// set the loginToken as session cookie
return new Cookie("loginToken", loginToken);
} else {
System.out.println("The login data is invalid!");
return null; // redirect so the input files get cleared, otherwise only pwd gets cleared
}
}
}

View File

@ -1,28 +0,0 @@
package org.hso.ecommerce.db;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import javax.sql.DataSource;
@Configuration
@EnableJpaRepositories(basePackages = "org.hso.ecommerce.db")
public class CustomerConfig {
@Autowired Environment env;
@Bean
public DataSource dataSource() {
final DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(env.getProperty("spring.datasource.driverClassName"));
dataSource.setUrl(env.getProperty("spring.datasource.url"));
dataSource.setUsername(env.getProperty("spring.datasource.user"));
dataSource.setPassword(env.getProperty("spring.datasource.password"));
return dataSource;
}
}

View File

@ -1,20 +0,0 @@
package org.hso.ecommerce.db;
import org.hso.ecommerce.entities.Customer;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface CustomerRepository extends JpaRepository<Customer, Long> {
@Query("SELECT c FROM Customer c WHERE c.lastname = :lastname")
List<Customer> findByLastname(String lastname);
@Query("SELECT c FROM Customer c WHERE c.username = :username")
List<Customer> findByUsername(String username);
}

View File

@ -1,154 +0,0 @@
package org.hso.ecommerce.db;
import java.sql.Types;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.function.SQLFunctionTemplate;
import org.hibernate.dialect.function.StandardSQLFunction;
import org.hibernate.dialect.function.VarArgsSQLFunction;
import org.hibernate.type.StringType;
public class SQLiteDialect extends Dialect {
public SQLiteDialect() {
registerColumnType(Types.BIT, "integer");
registerColumnType(Types.TINYINT, "tinyint");
registerColumnType(Types.SMALLINT, "smallint");
registerColumnType(Types.INTEGER, "integer");
registerColumnType(Types.BIGINT, "bigint");
registerColumnType(Types.FLOAT, "float");
registerColumnType(Types.REAL, "real");
registerColumnType(Types.DOUBLE, "double");
registerColumnType(Types.NUMERIC, "numeric");
registerColumnType(Types.DECIMAL, "decimal");
registerColumnType(Types.CHAR, "char");
registerColumnType(Types.VARCHAR, "varchar");
registerColumnType(Types.LONGVARCHAR, "longvarchar");
registerColumnType(Types.DATE, "date");
registerColumnType(Types.TIME, "time");
registerColumnType(Types.TIMESTAMP, "timestamp");
registerColumnType(Types.BINARY, "blob");
registerColumnType(Types.VARBINARY, "blob");
registerColumnType(Types.LONGVARBINARY, "blob");
// registerColumnType(Types.NULL, "null");
registerColumnType(Types.BLOB, "blob");
registerColumnType(Types.CLOB, "clob");
registerColumnType(Types.BOOLEAN, "integer");
registerFunction("concat", new VarArgsSQLFunction(StringType.INSTANCE, "", "||", ""));
registerFunction("mod", new SQLFunctionTemplate(StringType.INSTANCE, "?1 % ?2"));
registerFunction("substr", new StandardSQLFunction("substr", StringType.INSTANCE));
registerFunction("substring", new StandardSQLFunction("substr", StringType.INSTANCE));
}
public boolean supportsIdentityColumns() {
return true;
}
/*
public boolean supportsInsertSelectIdentity() {
return true; // As specify in NHibernate dialect
}
*/
public boolean hasDataTypeInIdentityColumn() {
return false; // As specify in NHibernate dialect
}
/*
public String appendIdentitySelectToInsert(String insertString) {
return new StringBuffer(insertString.length()+30). // As specify in NHibernate dialect
append(insertString).
append("; ").append(getIdentitySelectString()).
toString();
}
*/
public String getIdentityColumnString() {
// return "integer primary key autoincrement";
return "integer";
}
public String getIdentitySelectString() {
return "select last_insert_rowid()";
}
public boolean supportsLimit() {
return true;
}
protected String getLimitString(String query, boolean hasOffset) {
return new StringBuffer(query.length() + 20).
append(query).
append(hasOffset ? " limit ? offset ?" : " limit ?").
toString();
}
public boolean supportsTemporaryTables() {
return true;
}
public String getCreateTemporaryTableString() {
return "create temporary table if not exists";
}
public boolean dropTemporaryTableAfterUse() {
return false;
}
public boolean supportsCurrentTimestampSelection() {
return true;
}
public boolean isCurrentTimestampSelectStringCallable() {
return false;
}
public String getCurrentTimestampSelectString() {
return "select current_timestamp";
}
public boolean supportsUnionAll() {
return true;
}
public boolean hasAlterTable() {
return false; // As specify in NHibernate dialect
}
public boolean dropConstraints() {
return false;
}
public String getAddColumnString() {
return "add column";
}
public String getForUpdateString() {
return "";
}
public boolean supportsOuterJoinForUpdate() {
return false;
}
public String getDropForeignKeyString() {
throw new UnsupportedOperationException("No drop foreign key syntax supported by SQLiteDialect");
}
public String getAddForeignKeyConstraintString(String constraintName,
String[] foreignKey, String referencedTable, String[] primaryKey,
boolean referencesPrimaryKey) {
throw new UnsupportedOperationException("No add foreign key syntax supported by SQLiteDialect");
}
public String getAddPrimaryKeyConstraintString(String constraintName) {
throw new UnsupportedOperationException("No add primary key syntax supported by SQLiteDialect");
}
public boolean supportsIfExistsBeforeTableName() {
return true;
}
public boolean supportsCascadeDelete() {
return false;
}
}

View File

@ -0,0 +1,18 @@
package org.hso.ecommerce.db.repos;
import org.hso.ecommerce.entities.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
@Query("SELECT c FROM User c WHERE c.email = :email")
Optional<User> findByEmail(String email);
}

View File

@ -1,57 +0,0 @@
package org.hso.ecommerce.entities;
import javax.persistence.*;
@Entity
@Table(name = "customers")
public class Customer {
@Id
@GeneratedValue(strategy=GenerationType.SEQUENCE, generator = "id_Sequence")
@SequenceGenerator(name = "id_Sequence", sequenceName = "ID_SEQ")
public Long id;
public String lastname;
public String firstname;
public String username;
public String password;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getLastname() {
return lastname;
}
public void setLastname(String lastname) {
this.lastname = lastname;
}
public String getFirstname() {
return firstname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}

View File

@ -0,0 +1,37 @@
package org.hso.ecommerce.entities;
import javax.persistence.*;
import org.springframework.security.crypto.bcrypt.BCrypt;
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Basic
public long id;
public java.sql.Timestamp created;
@Column(unique = true)
public String email;
public String passwordHash;
public boolean isActive;
public boolean isEmployee;
public boolean getsAds;
public boolean isB2B;
public long getId() {
return id;
}
public boolean validatePassword(String password) {
return BCrypt.checkpw(password, passwordHash);
}
public void setPassword(String password) {
passwordHash = BCrypt.hashpw(password, BCrypt.gensalt());
}
}

View File

@ -7,8 +7,16 @@ logging.level.org.springframework.web=WARN
# DATABASE
spring.datasource.url = jdbc:sqlite:./test.db
spring.datasource.driverClassName = org.sqlite.JDBC
spring.jpa.properties.hibernate.dialect = org.hso.ecommerce.db.SQLiteDialect
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.SQLiteDialect
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql = true
# Session
#spring.session.store-type=jdbc
#spring.session.jdbc.initialize-schema=always
#spring.session.jdbc.schema=classpath:org/springframework/session/jdbc/schema-@@platform@@.sql
#spring.session.jdbc.table-name=SPRING_SESSION
#server.servlet.session.persistent=true
# ----------------------------------------
# WEB PROPERTIES

View File

@ -14,17 +14,17 @@
<input type="text" placeholder="Nach Produkten suchen..." />
<button>Finden</button>
</form>
<a th:unless="${customer}" class="button" th:href="@{/login}">Login</a>
<div th:if="${customer}" class="dropdown">
<a th:unless="${user}" class="button" th:href="@{/login}">Login</a>
<div th:if="${user}" class="dropdown">
<a class="dropdown-button button">Mein Konto</a>
<div class="dropdown-content">
<a class="black button" th:href="@{/user/notifications/}">Benachrichtigungen</a>
<a class="black button" th:href="@{/user/}">Einstellungen</a>
<a class="black button" th:href="@{/user/orders/}">Meine Bestellungen</a>
<form th:if="${customer}" method="post" th:action="@{/logout}" class="no-margin"><button class="bg-black no-margin full-width">Abmelden</button></form>
<form th:if="${user}" method="post" th:action="@{/logout}" class="no-margin"><button class="bg-black no-margin full-width">Abmelden</button></form>
</div>
</div>
<a th:if="${customer}" class="button" th:href="@{/shop/checkout}">Warenkorb</a>
<a class="button" th:href="@{/shop/checkout}">Warenkorb</a>
</div>
<div th:if="${error}" class="error" id="error-msg">
<div class="content-width bar-flex">

View File

@ -11,6 +11,13 @@
<!-- <nav th:replace="fragments/header :: header">Header</nav> -->
<main class="content-width modal">
<form class="detailgrid s hero " method="post">
<div th:if="${error}" class="s error" id="error-msg">
<div class="content-width bar-flex">
<div th:text="${error}" class="spacer error">
Error
</div>
</div>
</div>
<div class="s">
<label for="username">Email Adresse</label>
<input class="full-width" type="text" name="username" placeholder="Email Adresse" id="username" required>

View File

@ -14,7 +14,7 @@
<body>
<nav th:replace="fragments/header :: header">Header</nav>
<main class="modal">
<form class="detailflex m" th:action="@{/register}">
<form class="detailflex m" th:action="@{/register}" method="POST">
<div>
<h1>Neuen Account erstellen</h1>
</div>
@ -43,7 +43,7 @@
<div class="col-2">
<div>
<label for="salutation">Anrede</label>
<input class="full-width" list="salutationsOpt" name="salutation" placeholder="Anrede" required />
<input class="full-width" list="salutationsOpt" name="salutation" id="salutation" placeholder="Anrede" required />
<datalist id="salutationsOpt">
<option value="Herr">
<option value="Frau">
@ -53,13 +53,13 @@
</div>
<div>
<label for="name">Name</label>
<input class="full-width" type="text" name="name" placeholder="Nachname Vorname" required />
<input class="full-width" type="text" name="name" id="name" placeholder="Nachname Vorname" required />
</div>
</div>
<div>
<label for="address">Anschrift</label>
<textarea rows="5" class="full-width" type="text" name="address" placeholder="Optional: Zusatz&#10;Optional: Unternehmen&#10;Straße Hausnummer&#10;Postleitzeit Ort&#10;Land"></textarea>
<textarea rows="5" class="full-width" type="text" name="address" id="address" placeholder="Optional: Zusatz&#10;Optional: Unternehmen&#10;Straße Hausnummer&#10;Postleitzeit Ort&#10;Land"></textarea>
</div>
<fieldset>
<input type="radio" name="type" value="priv" id="type-priv" required>

View File

@ -123,7 +123,7 @@
<div>
<h1>Checkout</h1>
</div>
<div>
<div th:if="${user}">
<h2>Lieferadresse:</h2>
<textarea rows="5" class="full-width" type="text" name="address" placeholder="Optional: Zusatz&#10;Optional: Unternehmen&#10;Straße Hausnummer&#10;Postleitzeit Ort&#10;Land">
Max Mustermann
@ -131,7 +131,7 @@ Musterstraße 42
42424 Mustertal
</textarea>
</div>
<div>
<div th:if="${user}">
<h2>Zahlung:</h2>
<fieldset>
<input type="radio" name="type" value="priv" id="payment-card" required checked>
@ -151,7 +151,7 @@ Musterstraße 42
<th>Artikel (Netto)</th>
<th>200,29&nbsp;EUR</th>
</tr>
<tr>
<tr th:if="${user}">
<th>Bonuspunkte</th>
<th>-5,00&nbsp;EUR</th>
</tr>
@ -167,11 +167,17 @@ Musterstraße 42
<th>Gesamt:</th>
<th>240,79&nbsp;EUR</th>
</tr>
<tr class="secondary">
<th colspan="2" class=" no-padding"><button class=" no-margin secondary full-width">jetzt kostenpflichtig bestellen</button></th>
<tr th:if="${user}" class="secondary" >
<th colspan="2" class=" no-padding"></th>
</tr>
</table>
</div>
<div th:if="${user}">
<button class=" no-margin secondary full-width">jetzt kostenpflichtig bestellen</button>
</div>
<div th:unless="${user}">
<a th:href="@{/login}" class="button secondary no-margin full-width">Einloggen und forfahren.</a>
</div>
</div>
</form>
</main>