Adds functional login, signup and auth

This commit is contained in:
Julien Lengrand-Lambert
2022-08-17 16:27:36 +02:00
parent f6427b882c
commit a9c80b1621
4 changed files with 153 additions and 76 deletions

View File

@@ -18,6 +18,7 @@ export class PluckrLogin extends LitElement {
static get properties() {
return {
signUpOpened: {type: Boolean},
logInOpened: {type: Boolean},
username: {type: String},
password: {type: String},
passwordConfirm: {type: String},
@@ -33,6 +34,7 @@ export class PluckrLogin extends LitElement {
constructor() {
super();
this.signUpOpened = false;
this.logInOpened = false;
this.loadingBarActive = false;
}
@@ -40,7 +42,7 @@ export class PluckrLogin extends LitElement {
return html`
<vaadin-horizontal-layout slot="navbar touch-optimized" theme="spacing padding">
<vaadin-button theme="primary" @click="${this.signUpClicked}">Sign up</vaadin-button>
<vaadin-button theme="secondary">Login</vaadin-button>
<vaadin-button theme="secondary" @click="${this.logInClicked}">Login</vaadin-button>
</vaadin-horizontal-layout>
@@ -50,14 +52,52 @@ export class PluckrLogin extends LitElement {
@opened-changed="${e => (this.signUpOpened = e.detail.value)}"
${dialogRenderer(this.signUpRenderer, [this.username, this.signUpOpened, this.password, this.loadingBarActive, this.errorMessage])}
id="signUp">
</vaadin-dialog>
<vaadin-dialog
header-title="LogIn"
.opened="${this.logInOpened}"
@opened-changed="${e => (this.logInOpened = e.detail.value)}"
${dialogRenderer(this.logInRenderer, [this.username, this.logInOpened, this.password, this.loadingBarActive, this.errorMessage])}
id="logIn">
</vaadin-dialog>
`;
}
signUpClicked() {
this.signUpOpened = true;
console.log("Signup clicked!");
}
logInClicked() {
this.logInOpened = true;
}
logInRenderer(){
return html`
<vaadin-form-layout .responsiveSteps="${this.responsiveSteps}">
<vaadin-text-field value="${this.username}" @change="${(e) => {
this.username = e.target.value
}}" label="email"></vaadin-text-field>
<vaadin-password-field value="${this.password}" @change="${(e) => {
this.password = e.target.value
}}" label="Password"></vaadin-password-field>
${ this.loadingBarActive ?
html`<vaadin-progress-bar indeterminate></vaadin-progress-bar>`
: undefined
}
${ this.errorMessage ?
html`<p class="error" style="color:red">${this.errorMessage}</p>`
: undefined
}
<vaadin-horizontal-layout theme="spacing padding" style="justify-content: end">
<vaadin-button theme="secondary" @click="${() => {this.logInOpened = false}}">Cancel</vaadin-button>
<vaadin-button theme="primary" @click="${this.logIn}">LogIn</vaadin-button>
</vaadin-horizontal-layout>
</vaadin-form-layout>
`;
}
signUpRenderer() {
@@ -94,6 +134,42 @@ export class PluckrLogin extends LitElement {
`;
}
logIn(){
this.loadingBarActive = true;
fetch('/api/login', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
'username': this.username,
'password': this.password,
})
})
.then((response) => {
this.loadingBarActive = false;
if (!response.ok) {
this.errorMessage = response.statusText;
console.error('There has been a problem logging in the user:', response.statusText);
}
else{
this.username = null;
this.password = null;
this.passwordConfirm = null;
this.logInOpened = false;
}
})
.catch(error => {
this.errorMessage = "There has been an issue contacting the server. Please try again later"
console.error('There has been a problem with your fetch operation:', error);
this.username = null;
this.password = null;
this.passwordConfirm = null;
});
}
signUp() {
this.loadingBarActive = true;
@@ -123,6 +199,9 @@ export class PluckrLogin extends LitElement {
.catch(error => {
this.errorMessage = "There has been an issue contacting the server. Please try again later"
console.error('There has been a problem with your fetch operation:', error);
this.username = null;
this.password = null;
this.passwordConfirm = null;
});
}
}

View File

@@ -5,43 +5,49 @@ import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.engine.*
import io.ktor.server.metrics.micrometer.*
import io.ktor.server.netty.*
import io.ktor.server.plugins.callloging.*
import io.ktor.server.plugins.contentnegotiation.*
import io.ktor.server.plugins.cors.routing.*
import io.ktor.server.response.*
import io.ktor.server.sessions.*
import kotlinx.serialization.json.Json
import net.postgis.jdbc.geometry.Point
import nl.lengrand.pluckr.plugins.*
import org.jetbrains.exposed.sql.*
import nl.lengrand.pluckr.plugins.configureRouting
import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.SchemaUtils
import org.jetbrains.exposed.sql.StdOutSqlLogger
import org.jetbrains.exposed.sql.addLogger
import org.jetbrains.exposed.sql.transactions.transaction
import org.mindrot.jbcrypt.BCrypt
import org.mindrot.jbcrypt.BCrypt.*
fun Application.myapp(){
val database = initDb()
install(Sessions) {
cookie<UserSession>("user_session") {
cookie<UserSession>("user_session", SessionStorageMemory()) {
cookie.path = "/"
cookie.maxAgeInSeconds = 6000
}
}
install(Authentication) {
session<UserSession>("auth-session") {
session<UserSession>("user_session") {
println("validating session!")
validate { session ->
if(session.name.startsWith("jet")) {
if(session.name.isNotEmpty()) {
println("Valid!")
println(session)
session
} else {
println("Not valid!")
println(session)
null
}
}
challenge {
call.respondRedirect("/login")
println("redirecting")
call.respondRedirect("/")
}
}
}
@@ -55,7 +61,6 @@ fun Application.myapp(){
}
install(CallLogging)
// install(MicrometerMetrics)
configureRouting(database)
}
@@ -65,40 +70,7 @@ fun initDb(): Database {
transaction {
addLogger(StdOutSqlLogger)
SchemaUtils.create(Trees, Users)
// val salt = gensalt()
// println(salt)
// val user = Users.insert {
// it[username] = "bobby2@gmail.com"
// it[password] = hashpw("aVerySecretPassword", salt);
// }
// Users.selectAll().map {
// println("${it[Users.username]}, ${it[Users.password]}, ${it[Users.createdAt]}, ${it[Users.updatedAt]}")
//// if (checkpw("booby", it[Users.password]))
//// println("It matches");
//// else
//// println("It does not match");
// println("-----")
// }
// val first = Tree.new {
// name = "Laurier"
// description = "un laurier accessible à tous"
// location = Point(52.04681865145196, 5.079779509938945)
// }
// Trees.insert {
// it[name] = "Laurier 2"
// it[description] = "un laurier accessible à tous"
// it[location] = Point(52.04681865145196, 5.079779509938945)
// }
// println("Trees: ${Tree.all().joinToString {it.location.value}}")
}
return database
}

View File

@@ -7,15 +7,12 @@ import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import net.postgis.jdbc.geometry.Point
import nl.lengrand.pluckr.Trees
import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.ResultRow
import org.jetbrains.exposed.sql.selectAll
import org.jetbrains.exposed.sql.transactions.transaction
import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.toKotlinLocalDateTime
import nl.lengrand.pluckr.Users
import org.jetbrains.exposed.sql.insert
import org.mindrot.jbcrypt.BCrypt.gensalt
import org.mindrot.jbcrypt.BCrypt.hashpw
import org.jetbrains.exposed.sql.*
import org.mindrot.jbcrypt.BCrypt.*
@Serializable
data class UserSession(val name: String) : Principal
@@ -56,8 +53,12 @@ object PointSerializer : KSerializer<Point> {
}
}
private fun fromRow(it: ResultRow): Tree {
return Tree(it[Trees.id], it[Trees.name], it[Trees.description], it[Trees.location])
private fun ResultRow.toTree(): Tree {
return Tree(this[Trees.id], this[Trees.name], this[Trees.description], this[Trees.location])
}
private fun ResultRow.toUser(): User {
return User(this[Users.id], this[Users.username], this[Users.password], this[Users.createdAt].toKotlinLocalDateTime(), this[Users.updatedAt].toKotlinLocalDateTime())
}
class UserController(private val database: Database){
@@ -65,25 +66,41 @@ class UserController(private val database: Database){
val salt = gensalt()
transaction(database) {
Users.insert {
it[username] = email
it[password] = hashpw(zepassword, salt);
}
it[username] = email
it[password] = hashpw(zepassword, salt);
}
}
}
/*
Will throw NoSuchElementException if there are no results, or IllegalArgumentException if there are more than one
*/
private fun getUser(email: String): User {
val user = transaction(database) {
Users.select{ Users.username eq email}.single().toUser()
}
return user
}
fun getUser(email: String, zepassword: String): User {
val user = getUser(email)
if (!checkpw(zepassword, user.password)) throw AuthenticationException("Incorrect password detected")
return user
}
}
class TreeController(private val database: Database) {
fun getTrees() : ArrayList<Tree> {
val trees : ArrayList<Tree> = arrayListOf()
transaction(database){
Trees.selectAll().map { trees.add(fromRow(it)) }
Trees.selectAll().map { trees.add(it.toTree()) }
}
return trees
}
fun getTrees(bbox: List<Double>?) : ArrayList<Tree> {
println(bbox)
return getTrees()
}
}
}
class AuthenticationException(message:String): Exception(message)

View File

@@ -13,7 +13,6 @@ import io.ktor.server.response.*
import io.ktor.server.sessions.*
import org.jetbrains.exposed.exceptions.ExposedSQLException
import org.jetbrains.exposed.sql.Database
import java.sql.SQLIntegrityConstraintViolationException
fun Application.configureRouting(database: Database) {
@@ -22,21 +21,37 @@ fun Application.configureRouting(database: Database) {
routing {
authenticate("auth-form") {
post("/api/login") {
val userName = call.principal<UserIdPrincipal>()?.name.toString()
call.sessions.set(UserSession(name = userName))
call.respondRedirect("/hello")
authenticate("user_session") {
get("/api/authenticated"){
call.respondText("Hello, ${call.principal<UserSession>()?.name}!")
}
}
post("/api/login") {
val formParameters = call.receiveParameters()
try{
val user = userController.getUser(formParameters["username"].toString(), formParameters["password"].toString())
call.sessions.set(UserSession(user.username))
call.respondRedirect("/")
}
catch(e: ExposedSQLException){
call.response.status(HttpStatusCode(500, e.message!!))
}
}
post("/api/logout") {
call.sessions.clear<UserSession>()
call.respondRedirect("/")
}
post("/api/signup"){
val formParameters = call.receiveParameters()
try{
userController.createUser(formParameters["username"].toString(), formParameters["password"].toString())
call.response.status(HttpStatusCode.OK)
}
catch(e: ExposedSQLException){
catch(e: ExposedSQLException){ // TODO: Should I leak exceptions here?
val message = when (e.sqlState) {
"23505" ->
"User already exists"
@@ -44,15 +59,11 @@ fun Application.configureRouting(database: Database) {
"Unknown error, please retry later"
}
call.response.status(HttpStatusCode(500, message))
}
}
get("/api/trees") {
println("IN HERE FIRST")
if(call.request.queryParameters["bbox"] != null){
println("IN HERE")
val bbox = call.request.queryParameters["bbox"]?.split(",")?.map { it.toDouble() }
call.respond(treeController.getTrees(bbox))
}
@@ -61,9 +72,7 @@ fun Application.configureRouting(database: Database) {
}
}
get("/hello") {
get("/api/hello") {
call.respondText("Hello the World!")
}