mirror of
https://github.com/jlengrand/pluckr.git
synced 2026-03-10 08:41:17 +00:00
Adds functional login, signup and auth
This commit is contained in:
@@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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!")
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user