Adds registration.

This commit is contained in:
Stefan Schallerl 2025-02-07 14:53:17 +01:00
parent 52e4568fcc
commit 5356f47c9c
10 changed files with 205 additions and 67 deletions

View file

@ -11,7 +11,7 @@ class FilemureApp(repository: SqliteRepository) {
private val modifiers = TemplateModifiers() private val modifiers = TemplateModifiers()
private val loginPageController = LoginController(modifiers, repository) private val loginPageController = AccountController(modifiers, repository)
private val overviewController = OverviewController(modifiers, repository) private val overviewController = OverviewController(modifiers, repository)
private val limboController = LimboController(modifiers, repository) private val limboController = LimboController(modifiers, repository)
@ -33,6 +33,9 @@ class FilemureApp(repository: SqliteRepository) {
server.post("/login", loginPageController::doLogin, Role.ANON, Role.USER) server.post("/login", loginPageController::doLogin, Role.ANON, Role.USER)
server.post("/logout", loginPageController::doLogout, Role.USER) server.post("/logout", loginPageController::doLogout, Role.USER)
server.get("/register", loginPageController::formRegister, Role.ANON, Role.USER)
server.post("/register", loginPageController::doRegister, Role.ANON, Role.USER)
server.post("/upload", uploadController::upload, Role.USER) server.post("/upload", uploadController::upload, Role.USER)
server.get("/limbo", limboController::formLimbo, Role.USER) server.get("/limbo", limboController::formLimbo, Role.USER)

View file

@ -2,6 +2,13 @@ package net.h34t.filemure
import java.time.LocalDateTime import java.time.LocalDateTime
data class AccountRef(
val id: Long,
val extId: ExtId,
val email: String,
val created: LocalDateTime,
val state: State
)
@JvmInline @JvmInline
value class ExtId(val value: String) { value class ExtId(val value: String) {

View file

@ -0,0 +1,82 @@
package net.h34t.filemure.controller
import io.javalin.http.BadRequestResponse
import io.javalin.http.Context
import net.h34t.filemure.*
import net.h34t.filemure.repository.SqliteRepository
import net.h34t.filemure.tpl.Frame
import net.h34t.filemure.tpl.Login
import net.h34t.filemure.tpl.Register
class AccountController(private val modifiers: TemplateModifiers, private val repository: SqliteRepository) {
fun formLogin(ctx: Context) {
val loginError = ctx.queryParam("login_error") != null
ctx.tempolin(
Frame(
modifiers = modifiers,
title = "Login",
target = "",
back = "",
logout = false,
content = Login(
loginError = loginError
)
)
)
}
fun doLogin(ctx: Context) {
val username = ctx.formParam("username") ?: throw BadRequestResponse()
val password = ctx.formParam("password") ?: throw BadRequestResponse()
val acc = repository.loginAccount(email = username, password = password.trim().toCharArray())
if (acc != null) {
ctx.setSession(Session(id = acc.id, email = acc.email))
ctx.redirectPRG("/")
} else {
ctx.setSession(null)
ctx.redirectPRG("/login/?login_error")
}
}
fun doLogout(ctx: Context) {
ctx.setSession(null)
ctx.redirectPRG("/login")
}
fun formRegister(ctx: Context) {
val registerError = ctx.queryParam("register_error") != null
ctx.tempolin(
Frame(
modifiers = modifiers,
title = "Register",
target = "",
back = "/",
logout = false,
content = Register(
registerError = registerError
)
)
)
}
fun doRegister(ctx: Context) {
val username = ctx.formParam("username") ?: throw BadRequestResponse()
val password = ctx.formParam("password") ?: throw BadRequestResponse()
try {
require(username.isNotBlank())
require(password.isNotBlank() && password.length >= 6)
repository.addAccount(email = username, password = password.trim().toCharArray())
ctx.redirectPRG("/login")
} catch (e: Exception) {
e.printStackTrace()
ctx.redirectPRG("/register?register_error")
}
}
}

View file

@ -1,41 +0,0 @@
package net.h34t.filemure.controller
import io.javalin.http.Context
import net.h34t.filemure.*
import net.h34t.filemure.repository.SqliteRepository
import net.h34t.filemure.tpl.Frame
import net.h34t.filemure.tpl.Login
class LoginController(val modifiers: TemplateModifiers, val repository: SqliteRepository) {
fun formLogin(ctx: Context) {
ctx.tempolin(
Frame(
modifiers = modifiers,
title = "Hello to Filemure",
target = "",
back = "",
logout = false,
content = Login()
)
)
}
fun doLogin(ctx: Context) {
val username = ctx.formParam("username")
val password = ctx.formParam("password")
if (username == "stefan@schallerl.com" && password == "foobar") {
ctx.setSession(Session(id = 1, email = username))
ctx.redirectPRG("/")
} else {
ctx.setSession(null)
ctx.redirectPRG("/")
}
}
fun doLogout(ctx: Context) {
ctx.setSession(null)
ctx.redirectPRG("/login")
}
}

View file

@ -3,8 +3,10 @@ package net.h34t.filemure.repository
import app.cash.sqldelight.ColumnAdapter import app.cash.sqldelight.ColumnAdapter
import app.cash.sqldelight.db.SqlDriver import app.cash.sqldelight.db.SqlDriver
import app.cash.sqldelight.driver.jdbc.sqlite.JdbcSqliteDriver import app.cash.sqldelight.driver.jdbc.sqlite.JdbcSqliteDriver
import at.favre.lib.crypto.bcrypt.BCrypt
import net.h34t.filemure.* import net.h34t.filemure.*
import net.h34t.filemure.TagAdapter.serialize import net.h34t.filemure.TagAdapter.serialize
import net.h34t.filemure.db.Account
import net.h34t.filemure.db.Database import net.h34t.filemure.db.Database
import net.h34t.filemure.db.File_ import net.h34t.filemure.db.File_
import java.io.InputStream import java.io.InputStream
@ -54,6 +56,11 @@ class SqliteRepository(url: String) {
database = Database( database = Database(
driver = driver, driver = driver,
accountAdapter = Account.Adapter(
ext_idAdapter = extIdAdapter,
createdAdapter = localDateTimeAdapter,
stateAdapter = stateAdapter
),
documentAdapter = net.h34t.filemure.db.Document.Adapter( documentAdapter = net.h34t.filemure.db.Document.Adapter(
tagsAdapter = tagsAdapter, tagsAdapter = tagsAdapter,
createdAdapter = localDateTimeAdapter, createdAdapter = localDateTimeAdapter,
@ -69,6 +76,47 @@ class SqliteRepository(url: String) {
) )
} }
private val bcrypt = BCrypt.withDefaults()
private fun generateExtId(): ExtId = ExtId.generate()
fun addAccount(
email: String,
password: CharArray
) {
val bcryptPassword = bcrypt.hashToString(6, password)
val extId = generateExtId()
database.databaseQueries.addAccount(
extId = extId,
email = email,
password = bcryptPassword,
state = State.ACTIVE
)
}
fun loginAccount(email: String, password: CharArray): AccountRef? {
return database.databaseQueries.loginAccount(email).executeAsList().firstOrNull()?.let { account ->
if (account.state != State.ACTIVE)
return null
val verify = BCrypt.verifyer().verify(password, account.password)
if (verify.verified) {
account.let {
AccountRef(
id = it.id,
extId = it.ext_id,
email = it.email,
created = it.created,
state = it.state
)
}
} else {
null
}
}
}
fun addFileToLimbo( fun addFileToLimbo(
accountId: Long, accountId: Long,
filename: String, filename: String,
@ -77,7 +125,7 @@ class SqliteRepository(url: String) {
content: InputStream content: InputStream
): IdPair = ): IdPair =
database.databaseQueries.transactionWithResult { database.databaseQueries.transactionWithResult {
val extId = ExtId.generate() val extId = generateExtId()
database.databaseQueries.insertFileIntoLimbo( database.databaseQueries.insertFileIntoLimbo(
account_id = accountId, account_id = accountId,
ext_id = extId, ext_id = extId,
@ -102,7 +150,7 @@ class SqliteRepository(url: String) {
content: InputStream content: InputStream
) = ) =
database.databaseQueries.transactionWithResult { database.databaseQueries.transactionWithResult {
ExtId.generate().let { extId -> generateExtId().let { extId ->
database.databaseQueries.insertFileForDocument( database.databaseQueries.insertFileForDocument(
account_id = accountId, account_id = accountId,
document_id = documentId, document_id = documentId,
@ -148,7 +196,7 @@ class SqliteRepository(url: String) {
description: String, description: String,
fileExtIds: List<ExtId> fileExtIds: List<ExtId>
): ExtId { ): ExtId {
val extId = ExtId.generate() val extId = generateExtId()
database.databaseQueries.transaction { database.databaseQueries.transaction {
database.databaseQueries.addDocument( database.databaseQueries.addDocument(
account_id = accountId, account_id = accountId,

View file

@ -8,7 +8,7 @@ import net.h34t.filemure.Tag;
CREATE TABLE account ( CREATE TABLE account (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
ext_id TEXT AS ExtId NOT NULL, ext_id TEXT AS ExtId NOT NULL,
email TEXT NOT NULL, email TEXT NOT NULL,
password TEXT NOT NULL, password TEXT NOT NULL,
created TEXT AS LocalDateTime DEFAULT (CURRENT_TIMESTAMP) NOT NULL, created TEXT AS LocalDateTime DEFAULT (CURRENT_TIMESTAMP) NOT NULL,
@ -59,6 +59,12 @@ CREATE TABLE file (
CREATE INDEX file_state_IDX ON file (state); CREATE INDEX file_state_IDX ON file (state);
CREATE UNIQUE INDEX file_ext_id_IDX ON file (ext_id); CREATE UNIQUE INDEX file_ext_id_IDX ON file (ext_id);
addAccount:
INSERT INTO account (ext_id, email, password, state) VALUES (:extId, :email, :password, :state);
loginAccount:
SELECT id, ext_id, email, password, created, state FROM account WHERE email=:email;
--- ---
insertFileForDocument: insertFileForDocument:
INSERT INTO file (account_id, document_id, ext_id, filename, content_type, file_size, content) VALUES (?, ?,?,?,?,?,?); INSERT INTO file (account_id, document_id, ext_id, filename, content_type, file_size, content) VALUES (?, ?,?,?,?,?,?);

View file

@ -30,7 +30,13 @@
</a> </a>
</td> </td>
<td> <td>
<form action="/file/{*$extId}/delete?return=limbo" method="POST"> <form action="/document/new" method="get">
<input type="hidden" name="file_id" value="{*$extId}">
<button class="small"><i>add</i><span>document</span></button>
</form>
</td>
<td>
<form action="/file/{*$extId}/delete?return=limbo" method="post">
<button class="small"><i>delete</i><span>delete</span></button> <button class="small"><i>delete</i><span>delete</span></button>
</form> </form>
</td> </td>

View file

@ -1,7 +1,4 @@
<h1>Hello to Filemure</h1>
<form action="/login" method="post"> <form action="/login" method="post">
<fieldset> <fieldset>
<legend>Log In</legend> <legend>Log In</legend>
@ -21,6 +18,11 @@
</button> </button>
<!--<p><input type="submit" value="login"></p>--> <p>Or <a href="/register">register a new account</a>.</p>
{if $loginError}
<div class="snackbar error active">Email or password wrong or the account doesn't exist.</div>
{/if}
</fieldset> </fieldset>
</form> </form>

View file

@ -10,7 +10,9 @@
Files in <a href="/limbo">limbo: {$limboFileCount}</a> Files in <a href="/limbo">limbo: {$limboFileCount}</a>
</div> </div>
<div class="min"> <div class="min">
<a href="/limbo"><button><i>folder_open</i><span>view</span></button></a> <a href="/limbo">
<button><i>folder_open</i><span>view</span></button>
</a>
</div> </div>
</div> </div>
</fieldset> </fieldset>
@ -40,27 +42,25 @@
<table class="stripes"> <table class="stripes">
<thead> <thead>
<tr> <tr>
<th>Date</th> <th class="min">Date</th>
<th>Files</th> <th class="max">Files</th>
<th>View</th> <th class="right-align">View</th>
</tr> </tr>
</thead> </thead>
{for $year}
<thead>
<tr>
<th colspan="3">{*$year}</th>
</tr>
</thead>
{for $month}
<tbody> <tbody>
{for $year}
<tr> <tr>
<td>{*$monthHuman}</td> <th colspan="3"><h6>{*$year}</h6></th>
<td>{*$count}</td> </tr>
<td><a href="/overview/{*$year}/{*$month}"> {for $month}
<tr>
<td class="min">{*$monthHuman}</td>
<td class="max">{*$count}</td>
<td class="right-align"><a href="/overview/{*$year}/{*$month}">
<button class="small"><span>view</span></button> <button class="small"><span>view</span></button>
</a></td> </a></td>
</tr> </tr>
{/for}
{/for}
</tbody> </tbody>
{/for}
{/for}
</table> </table>

View file

@ -0,0 +1,25 @@
<form action="/register" method="post">
<fieldset>
<legend>Register</legend>
<div class="field border label">
<input id="email" type="email" placeholder="your-email@example.com" name="username">
<label for="email">Your email</label>
</div>
<div class="field border label">
<input id="password" type="password" name="password">
<label for="password">Your password</label>
</div>
<button>
<i>add</i>
<span>register</span>
</button>
{if $registerError}
<div class="snackbar error active">Email or password invalid or account already exists.</div>
{/if}
</fieldset>
</form>