aboutsummaryrefslogtreecommitdiff
path: root/src/main/kotlin
diff options
context:
space:
mode:
authorMarvin Borner2020-07-09 22:33:43 +0200
committerMarvin Borner2020-07-09 22:33:43 +0200
commit1edfdf5f3a316a36108a0a853b0a2553d116d6fc (patch)
tree4b825dc642cb6eb9a060e54bf8d69288fbee4904 /src/main/kotlin
parent18edde9bd3603f3f867cebce100e7b22be9012cd (diff)
Rewriiiiiiiiiite!
Okay, okay. I know, rewriting projects all the time is dumb. Buuut, we really don't want to work with our old and ugly code anymore. Furthermore we'll be using Deno now!
Diffstat (limited to 'src/main/kotlin')
-rw-r--r--src/main/kotlin/App.kt241
-rw-r--r--src/main/kotlin/CryptoHandler.kt51
-rw-r--r--src/main/kotlin/DatabaseController.kt511
-rw-r--r--src/main/kotlin/FileController.kt277
-rw-r--r--src/main/kotlin/LogFilter.kt28
-rw-r--r--src/main/kotlin/UserHandler.kt222
6 files changed, 0 insertions, 1330 deletions
diff --git a/src/main/kotlin/App.kt b/src/main/kotlin/App.kt
deleted file mode 100644
index 55058be..0000000
--- a/src/main/kotlin/App.kt
+++ /dev/null
@@ -1,241 +0,0 @@
-package space.anity
-
-import com.fizzed.rocker.*
-import com.fizzed.rocker.runtime.*
-import io.javalin.*
-import io.javalin.apibuilder.ApiBuilder.*
-import io.javalin.rendering.*
-import io.javalin.rendering.template.TemplateUtil.model
-import io.javalin.security.*
-import io.javalin.security.SecurityUtil.roles
-import org.slf4j.*
-import java.net.*
-import kotlin.system.*
-
-const val debug = false
-var silent = true
-var port = 7000
-// TODO: Add abstract and secure file home support for windows/BSD/macOS
-val fileHome = if (System.getProperty("os.name") != "Linux" || debug) "files" else "/usr/share/kloud/files"
-val databaseController = DatabaseController()
-val userHandler = UserHandler()
-val fileController = FileController()
-private val log = LoggerFactory.getLogger("App.kt")
-
-fun main(args: Array<String>) {
- val app = startServer(args)
- log.info("Successfully started server on port $port")
-
- // Set up templating
- RockerRuntime.getInstance().isReloading = false
- JavalinRenderer.register(
- FileRenderer { filepath, model -> Rocker.template(filepath).bind(model).render().toString() }, ".rocker.html"
- )
-
- databaseController.initDatabase()
-
- app.routes {
- /**
- * Normalizes and cleans the requested url
- */
- before("/*") { ctx ->
- if (URI(ctx.url()).normalize().toString() != ctx.url()) ctx.redirect(URI(ctx.url()).normalize().toString())
- }
-
- /**
- * Renders the static resources (important for deployed jar files)
- */
- get(
- "/css/*", { ctx ->
- ctx.contentType("text/css")
- try {
- ctx.result(Thread.currentThread().contextClassLoader.getResourceAsStream("css/" + ctx.splat(0)))
- } catch (_: Exception) {
- throw NotFoundResponse()
- }
- },
- roles(Roles.GUEST, Roles.USER)
- )
- get(
- "/js/*", { ctx ->
- ctx.contentType("text/javascript")
- try {
- ctx.result(Thread.currentThread().contextClassLoader.getResourceAsStream("js/" + ctx.splat(0)))
- } catch (_: Exception) {
- throw NotFoundResponse()
- }
- },
- roles(Roles.GUEST, Roles.USER)
- )
- get(
- "/fonts/*", { ctx ->
- try {
- ctx.result(Thread.currentThread().contextClassLoader.getResourceAsStream("fonts/" + ctx.splat(0)))
- } catch (_: Exception) {
- throw NotFoundResponse()
- }
- },
- roles(Roles.GUEST, Roles.USER)
- )
-
- /**
- * Main page
- */
- get("/", { ctx ->
- ctx.render(
- "index.rocker.html",
- model("username", databaseController.getUsername(userHandler.getVerifiedUserId(ctx)), "ctx", ctx)
- )
- }, roles(Roles.GUEST, Roles.USER))
-
- /**
- * Renders the login page
- */
- get("/user/login", userHandler::renderLogin, roles(Roles.GUEST, Roles.USER))
-
- /**
- * Endpoint for user authentication
- */
- post("/user/login", userHandler::login, roles(Roles.GUEST))
-
- /**
- * Logs the user out
- */
- get("/user/logout", userHandler::logout, roles(Roles.USER))
-
- /**
- * Toggles the users theme
- */
- post("/user/theme", userHandler::toggleTheme, roles(Roles.USER))
-
- /**
- * Renders the registration page
- */
- get("/user/register", userHandler::renderRegistration, roles(Roles.GUEST))
-
- /**
- * Registers new user
- */
- post("/user/register", userHandler::register, roles(Roles.GUEST))
-
- /**
- * Adds part of a new user (username) to database
- */
- post("/user/add", databaseController::indexUserRegistration, roles(Roles.ADMIN))
-
- /**
- * Renders the admin interface
- */
- get("/admin", userHandler::renderAdmin, roles(Roles.ADMIN))
-
- /**
- * Renders the setup page (only on initial use)
- */
- get("/setup", userHandler::renderSetup, roles(Roles.GUEST))
-
- /**
- * Endpoint for setup (only on initial use)
- */
- post("/setup", userHandler::setup, roles(Roles.GUEST))
-
- /**
- * Renders the file list view
- */
- get("/files/*", fileController::crawl, roles(Roles.USER))
-
- /**
- * Receives and saves multipart media data
- */
- post("/file/upload/*", fileController::upload, roles(Roles.USER))
-
- /**
- * Indexes every file of the user into the database
- */
- get("/file/index", fileController::indexAll, roles(Roles.USER))
-
- /**
- * Deletes file
- */
- post("/file/delete/*", fileController::delete, roles(Roles.USER))
-
- /**
- * Shares file
- */
- post("/file/share/*", fileController::share, roles(Roles.USER))
-
- /**
- * Shares file in directory
- */
- post("/file/share", fileController::handleSharedFile, roles(Roles.USER))
-
- /**
- * Shows the shared file
- */
- get("/file/shared", fileController::renderShared, roles(Roles.GUEST, Roles.USER))
- }
-}
-
-/**
- * Sets up the roles with the database and declares the handling of roles
- */
-fun roleManager(handler: Handler, ctx: Context, permittedRoles: Set<Role>) {
- if (userHandler.getVerifiedUserId(ctx) == ctx.cookieStore("userId") ?: "userId"
- && databaseController.getRoles(userHandler.getVerifiedUserId(ctx)).any { it in permittedRoles }
- ) handler.handle(ctx)
- else if (userHandler.getVerifiedUserId(ctx) != ctx.cookieStore("userId") ?: "userId"
- && databaseController.getRoles(userHandler.getVerifiedUserId(ctx)).any { it in permittedRoles }
- ) handler.handle(ctx)
- else ctx.status(401).redirect("/user/login")
-}
-
-/**
- * Starts the server and parses the command line arguments
- */
-fun startServer(args: Array<String>): Javalin {
- var runServer = true
-
- args.forEachIndexed { index, element ->
- run {
- val wantsDebug = element.startsWith("-d") || element.startsWith("--debug")
- val wantsPort = element.startsWith("-p") || element.startsWith("--port")
- val wantsHelp = element.startsWith("-h") || element.startsWith("--help")
-
- if (wantsPort) {
- val portArgument = args[index + 1].toInt()
- if (portArgument in 1..65535) port = portArgument
- }
- if (wantsDebug) {
- silent = false
- }
- if (wantsHelp) {
- runServer = false
- log.info(
- "Help: \n" +
- "Use -p or --port to specify a port\n" +
- "Use -d or --debug to enable debug mode\n" +
- "Use -h or --help to show this help message"
- )
- }
- }
- }
-
- return if (runServer) {
- try {
- Javalin.create().apply {
- port(port)
- accessManager { handler, ctx, permittedRoles -> roleManager(handler, ctx, permittedRoles) }
- disableStartupBanner()
- }.start()
- } catch (_: Exception) {
- log.info("Port $port already in use!")
- exitProcess(1)
- }
- } else exitProcess(1)
-}
-
-/**
- * Declares the roles in which a user can be in
- */
-enum class Roles : Role {
- ADMIN, USER, GUEST
-}
diff --git a/src/main/kotlin/CryptoHandler.kt b/src/main/kotlin/CryptoHandler.kt
deleted file mode 100644
index e3f33df..0000000
--- a/src/main/kotlin/CryptoHandler.kt
+++ /dev/null
@@ -1,51 +0,0 @@
-package space.anity
-
-import java.io.*
-import java.security.*
-import javax.crypto.*
-import javax.crypto.spec.*
-
-class CryptoHandler @Throws(NoSuchPaddingException::class, NoSuchAlgorithmException::class)
-internal constructor(private val secretKey: SecretKey, cipher: String) {
- private val cipher: Cipher = Cipher.getInstance(cipher)
-
- @Throws(InvalidKeyException::class, IOException::class)
- internal fun encrypt(content: String, fileName: String) {
- cipher.init(Cipher.ENCRYPT_MODE, secretKey)
- val iv = cipher.iv
-
- FileOutputStream(fileName).use { fileOut ->
- fileOut.write(iv)
- CipherOutputStream(fileOut, cipher).use { cipherOut ->
- cipherOut.write(content.toByteArray())
- }
- }
- }
-
- @Throws(InvalidAlgorithmParameterException::class, InvalidKeyException::class, IOException::class)
- internal fun decrypt(fileName: String): String {
- var content = ""
-
- FileInputStream(fileName).use { fileIn ->
- val iv = ByteArray(16)
- fileIn.read(iv)
- cipher.init(Cipher.DECRYPT_MODE, secretKey, IvParameterSpec(iv))
-
- CipherInputStream(fileIn, cipher).use { cipherIn ->
- InputStreamReader(cipherIn, Charsets.UTF_8).use { inputReader ->
- BufferedReader(inputReader).use { reader ->
- val sb = StringBuilder()
- var line: String? = reader.readLine()
- while (line != null) {
- sb.append(line + "\n")
- line = reader.readLine()
- }
- content = sb.toString()
- }
- }
- }
- }
-
- return content // TODO: Fix char handling as 1 byte in decryption
- }
-}
diff --git a/src/main/kotlin/DatabaseController.kt b/src/main/kotlin/DatabaseController.kt
deleted file mode 100644
index 5316bb0..0000000
--- a/src/main/kotlin/DatabaseController.kt
+++ /dev/null
@@ -1,511 +0,0 @@
-package space.anity
-
-import at.favre.lib.crypto.bcrypt.*
-import io.javalin.*
-import io.javalin.rendering.template.TemplateUtil.model
-import org.jetbrains.exposed.sql.*
-import org.jetbrains.exposed.sql.transactions.*
-import org.joda.time.*
-import org.slf4j.*
-import java.sql.*
-
-class DatabaseController {
- private val dbFileLocation =
- if (System.getProperty("os.name") != "Linux" || debug) "main.db" else "/usr/share/kloud/main.db"
- val db: Database = Database.connect("jdbc:sqlite:$dbFileLocation", "org.sqlite.JDBC")
- private val log = LoggerFactory.getLogger(this.javaClass.name)
-
- /**
- * Database table indexing the file locations
- */
- object FileLocation : Table() {
- val id = integer("id").autoIncrement().primaryKey()
- val path = text("path")
- val isDirectory = bool("isDirectory").default(false)
- val userId = integer("userId").references(UserData.id)
- val accessId = varchar("accessId", 64).uniqueIndex()
- val isShared = bool("isShared").default(false)
- }
-
- /**
- * Database table indexing the users with their regarding passwords
- */
- object UserData : Table() {
- val id = integer("id").autoIncrement().primaryKey()
- val username = varchar("username", 24).uniqueIndex()
- val password = varchar("password", 64)
- val verification = varchar("verification", 64).uniqueIndex()
- val darkTheme = bool("darkTheme").default(false)
- }
-
- /**
- * Database table indexing the users with their regarding role (multi line per user)
- */
- object UserRoles : Table() {
- val id = integer("id").autoIncrement().primaryKey()
- val userId = integer("userId").references(UserData.id)
- val roleId = integer("role").references(RolesData.id)
- }
-
- /**
- * Database table indexing the soon-to-be registered users by username
- */
- object UserRegistration : Table() {
- val id = integer("id").autoIncrement().primaryKey()
- val username = varchar("username", 24).uniqueIndex()
- val token = varchar("token", 64).uniqueIndex()
- }
-
- /**
- * Database table declaring available roles
- */
- object RolesData : Table() {
- val id = integer("id").autoIncrement().primaryKey()
- val role = varchar("roles", 16)
- }
-
- /**
- * Database table indexing the login attempts of an ip in combination with the timestamp
- */
- object LoginAttempts : Table() {
- val id = integer("id").autoIncrement().primaryKey()
- val ip = varchar("ip", 16)
- val timestamp = datetime("timestamp")
- }
-
- /**
- * Database table storing general data/states
- */
- object General : Table() {
- val id = integer("id").autoIncrement().primaryKey()
- val initialUse = bool("initialUse").default(true)
- val isSetup = bool("isSetup").default(false)
- }
-
- init {
- // Create connection
- TransactionManager.manager.defaultIsolationLevel = Connection.TRANSACTION_SERIALIZABLE
-
- // Add tables
- transaction {
- SchemaUtils.createMissingTablesAndColumns(
- FileLocation,
- UserData,
- UserRoles,
- UserRegistration,
- RolesData,
- LoginAttempts,
- General
- )
- }
- }
-
- /**
- * Creates the user in the database using username, password and the role
- */
- fun createUser(usernameString: String, passwordString: String, roleString: String): Boolean {
- return transaction {
- try {
- val usersId = UserData.insert {
- it[username] = usernameString
- it[password] = BCrypt.withDefaults().hashToString(12, passwordString.toCharArray())
- it[verification] = generateRandomString()
- }[UserData.id]
-
- UserRoles.insert { roles ->
- roles[userId] = usersId!!
- roles[roleId] = RolesData.select { RolesData.role eq roleString }.map { it[RolesData.id] }[0]
- }
- true
- } catch (_: Exception) {
- log.warn("User already exists!")
- false
- }
- }
- }
-
- /**
- * Checks whether the user is allowed to register
- */
- fun isUserRegistrationValid(usernameString: String, tokenString: String): Boolean {
- return transaction {
- try {
- if (UserData.select { UserData.username eq usernameString }.empty() &&
- UserRegistration.select { UserRegistration.token eq tokenString }.map { it[UserRegistration.token] }[0] == tokenString
- ) {
- usernameString == UserRegistration.select { UserRegistration.username eq usernameString }.map { it[UserRegistration.username] }[0]
- } else false
- } catch (err: Exception) {
- log.error(err.toString())
- false
- }
- }
- }
-
- /**
- * Adds a user to the registration table
- */
- fun indexUserRegistration(ctx: Context) {
- val usernameString = ctx.formParam("username", "").toString()
-
- if (usernameString.matches("[a-zA-Z0-9]+".toRegex()) && usernameString.length > 3) {
- val tokenString = generateRandomString()
- var error = false
-
- transaction {
- try {
- UserRegistration.insert {
- it[username] = usernameString
- it[token] = tokenString
- }
- } catch (err: Exception) {
- log.error(err.toString())
- error = true
- }
- }
-
- if (error) ctx.render("admin.rocker.html", model("message", "User already exists!", "ctx", ctx))
- else ctx.render(
- "admin.rocker.html", model(
- "message", "http://${ctx.host()}/user/register?username=$usernameString&token=$tokenString",
- "ctx", ctx
- )
- )
- } else ctx.render("admin.rocker.html", model("message", "Please only use alphabetical characters!", "ctx", ctx))
- }
-
- /**
- * Removes the registration index of [usernameString]
- */
- fun removeRegistrationIndex(usernameString: String) {
- transaction {
- UserRegistration.deleteWhere { UserRegistration.username eq usernameString }
- }
- }
-
- /**
- * Tests whether the password [passwordString] of the user [usernameString] is correct
- */
- fun checkUser(usernameString: String, passwordString: String): Boolean {
- return transaction {
- try {
- val passwordHash =
- UserData.select { UserData.username eq usernameString }.map { it[UserData.password] }[0]
- BCrypt.verifyer().verify(passwordString.toCharArray(), passwordHash).verified
- } catch (err: Exception) {
- log.error(err.toString())
- false
- }
- }
- }
-
- /**
- * Returns the corresponding username using [userId]
- */
- fun getUsername(userId: Int): String {
- return transaction {
- try {
- UserData.select { UserData.id eq userId }.map { it[UserData.username] }[0]
- } catch (err: Exception) {
- log.error(err.toString())
- ""
- }
- }
- }
-
- /**
- * Returns the corresponding username using [verificationId]
- */
- fun getUserIdByVerificationId(verificationId: String): Int {
- return transaction {
- try {
- UserData.select { UserData.verification eq verificationId }.map { it[UserData.id] }[0]
- } catch (err: Exception) {
- -1
- }
- }
- }
-
- /**
- * Returns true when user uses dark theme
- */
- fun isDarkTheme(userId: Int): Boolean {
- return transaction {
- try {
- UserData.select { UserData.id eq userId }.map { it[UserData.darkTheme] }[0]
- } catch (err: Exception) {
- false
- }
- }
- }
-
- /**
- * Toggles the dark theme
- */
- fun toggleDarkTheme(userId: Int) {
- return transaction {
- try {
- UserData.update({ (UserData.id eq userId) }) {
- it[darkTheme] = !isDarkTheme(userId)
- }
- } catch (err: Exception) {
- log.error(err.toString())
- }
- }
- }
-
- /**
- * Returns the corresponding verification id using [usernameString]
- */
- fun getVerificationId(usernameString: String): String {
- return transaction {
- try {
- UserData.select { UserData.username eq usernameString }.map { it[UserData.verification] }[0]
- } catch (err: Exception) {
- log.error(err.toString())
- ""
- }
- }
- }
-
- /**
- * Returns the corresponding userId using [usernameString]
- */
- fun getUserId(usernameString: String): Int {
- return transaction {
- try {
- UserData.select { UserData.username eq usernameString }.map { it[UserData.id] }[0]
- } catch (err: Exception) {
- log.error(err.toString())
- -1
- }
- }
- }
-
- /**
- * Returns the corresponding role using [userId]
- */
- fun getRoles(userId: Int): List<Roles> {
- return transaction {
- try {
- val userRoleId = UserRoles.select { UserRoles.userId eq userId }.map { it[UserRoles.roleId] }[0]
-
- val userRoles = mutableListOf<Roles>()
- RolesData.select { RolesData.id eq userRoleId }.map { it[RolesData.role] }.forEach {
- when (Roles.valueOf(it)) {
- Roles.GUEST -> {
- userRoles.add(Roles.GUEST)
- }
- Roles.USER -> {
- userRoles.add(Roles.USER)
- }
- Roles.ADMIN -> {
- userRoles.add(Roles.GUEST)
- userRoles.add(Roles.USER)
- userRoles.add(Roles.ADMIN)
- }
- }
- }
- userRoles
- } catch (err: Exception) {
- listOf(Roles.GUEST)
- }
- }
- }
-
- /**
- * Adds the uploaded file to the database
- */
- fun addFile(fileLocation: String, usersId: Int, isDirectoryBool: Boolean = false): Boolean {
- return transaction {
- try {
- if (FileLocation.select { (FileLocation.path eq fileLocation) and (FileLocation.userId eq usersId) }.empty()) {
- FileLocation.insert {
- it[path] = fileLocation
- it[userId] = usersId
- it[accessId] = generateRandomString()
- it[isDirectory] = isDirectoryBool
- }
- true
- } else {
- if (!isDirectoryBool && debug) log.warn("File already exists!")
- false
- }
- } catch (err: Exception) {
- error(err)
- }
- }
- }
-
- /**
- * Removes the file from the database
- */
- fun deleteFile(fileLocation: String, userId: Int) {
- transaction {
- try {
- // TODO: Think of new solution for directory deleting (instead of wildcards)
- FileLocation.deleteWhere { (FileLocation.path like "$fileLocation%") and (FileLocation.userId eq userId) }
- } catch (err: Exception) {
- log.error(err.toString())
- log.warn("File does not exist!")
- }
- }
- }
-
- /**
- * Returns the accessId of the given file
- */
- fun getAccessId(fileLocation: String, userId: Int): String {
- return transaction {
- try {
- FileLocation.update({ (FileLocation.userId eq userId) and (FileLocation.path like "$fileLocation%") }) {
- it[isShared] = true
- }
- FileLocation.select { (FileLocation.path eq fileLocation) and (FileLocation.userId eq userId) }.map { it[FileLocation.accessId] }[0]
- } catch (err: Exception) {
- log.error(err.toString())
- ""
- }
- }
- }
-
- /**
- * Returns accessId of file in directory
- */
- fun getAccessIdOfDirectory(filename: String, accessId: String): String {
- return transaction {
- try {
- val fileData =
- FileLocation.select {
- FileLocation.accessId eq accessId
- }.map { it[FileLocation.path] to it[FileLocation.userId] to it[FileLocation.isShared] }[0]
- if (fileData.second)
- FileLocation.select {
- (FileLocation.path eq "${fileData.first.first}${filename.substring(1)}") and (FileLocation.userId eq fileData.first.second)
- }.map { it[FileLocation.accessId] }[0]
- else ""
- } catch (err: Exception) {
- log.error(err.toString())
- ""
- }
- }
- }
-
- /**
- * Gets the shared file via [accessId]
- */
- fun getSharedFile(accessId: String): ReturnFileData {
- return transaction {
- try {
- if (FileLocation.select { FileLocation.accessId eq accessId }.map { it[FileLocation.isShared] }[0]) {
- val userId =
- FileLocation.select { FileLocation.accessId eq accessId }.map { it[FileLocation.userId] }[0]
- val fileLocation =
- FileLocation.select { FileLocation.accessId eq accessId }.map { it[FileLocation.path] }[0]
- val isDir =
- FileLocation.select { FileLocation.accessId eq accessId }.map { it[FileLocation.isDirectory] }[0]
- ReturnFileData(userId, fileLocation, isDir)
- } else
- ReturnFileData(-1, "", false)
- } catch (err: Exception) {
- log.error(err.toString())
- log.warn("File does not exist!")
- ReturnFileData(-1, "", false)
- }
- }
- }
-
- /**
- * Checks whether the site has been set up
- */
- fun isSetup(): Boolean {
- return transaction {
- try {
- General.selectAll().map { it[General.isSetup] }[0]
- } catch (err: Exception) {
- log.error(err.toString())
- false
- }
- }
- }
-
- /**
- * Toggles the setup state
- */
- fun toggleSetup() {
- transaction {
- General.update({ General.initialUse eq false }) {
- it[isSetup] = true
- }
- }
- }
-
- /**
- * Adds an login attempt to the database
- */
- fun loginAttempt(dateTime: DateTime, requestIp: String) {
- transaction {
- LoginAttempts.insert {
- it[timestamp] = dateTime
- it[ip] = requestIp
- }
- }
- }
-
- /**
- * Gets all login attempts of [requestIp]
- */
- fun getLoginAttempts(requestIp: String): List<Pair<DateTime, String>> {
- return transaction {
- LoginAttempts.select { LoginAttempts.ip eq requestIp }
- .map { it[LoginAttempts.timestamp] to it[LoginAttempts.ip] }
- }
- }
-
- /**
- * Initializes the database
- */
- fun initDatabase() {
- val initialUseRow = transaction { General.selectAll().map { it[General.initialUse] } }
- if (initialUseRow.isEmpty() || initialUseRow[0]) {
- transaction {
- RolesData.insert {
- it[role] = "ADMIN"
- }
- RolesData.insert {
- it[role] = "USER"
- }
- RolesData.insert {
- it[role] = "GUEST"
- }
-
- UserRoles.insert {
- it[userId] = 1
- it[roleId] = 1
- }
-
- General.insert {
- it[initialUse] = false
- }
- }
- } else {
- log.warn("Already initialized Database.")
- }
- }
-
- /**
- * Generates a random string with [length] characters
- */
- private fun generateRandomString(length: Int = 64): String {
- val allowedChars = "ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz0123456789"
- return (1..length)
- .map { allowedChars.random() }
- .joinToString("")
- }
-}
-
-data class ReturnFileData(
- val userId: Int,
- val fileLocation: String,
- val isDirectory: Boolean
-)
diff --git a/src/main/kotlin/FileController.kt b/src/main/kotlin/FileController.kt
deleted file mode 100644
index b1c26dd..0000000
--- a/src/main/kotlin/FileController.kt
+++ /dev/null
@@ -1,277 +0,0 @@
-package space.anity
-
-import io.javalin.*
-import io.javalin.rendering.template.TemplateUtil.model
-import org.slf4j.*
-import java.io.*
-import java.nio.charset.*
-import java.nio.file.*
-import java.text.*
-import javax.crypto.*
-import javax.crypto.spec.*
-
-class FileController {
- private val log = LoggerFactory.getLogger(this.javaClass.name)
-
- private val staticEncPwd = "asdffdsaasdffdsa"
- private val secretKey: SecretKey = SecretKeySpec(staticEncPwd.toByteArray(), "AES")
-
- /**
- * Crawls the requested file and either renders the directory view or the file view
- */
- fun crawl(ctx: Context) {
- try {
- val userId = userHandler.getVerifiedUserId(ctx)
- val usersFileHome = "$fileHome/$userId"
- val firstParam = ctx.splat(0) ?: ""
- val fileLocation = "$usersFileHome/$firstParam"
- File(fileLocation).mkdirs()
- when {
- ctx.queryParam("raw") != null -> ctx.result(decrypt(fileLocation))
- File(fileLocation).isDirectory -> {
- val files = ArrayList<Array<String>>()
- Files.list(Paths.get("$usersFileHome/$firstParam/")).forEach {
- val filename = it.toString()
- .drop(usersFileHome.length + (if (firstParam.isNotEmpty()) firstParam.length + 2 else 1))
- val filePath = "$usersFileHome${it.toString().drop(usersFileHome.length)}"
- files.add(addToFileListing(filePath, filename))
- }
- files.sortWith(compareBy { it.first() })
- ctx.render(
- "files.rocker.html", model(
- "files", files,
- "path", (if (firstParam.firstOrNull() == '/') firstParam.drop(1) else firstParam),
- "isShared", false,
- "ctx", ctx
- )
- )
- }
- // TODO: Fix decrypting every file when crawling (human readable flag in db?)
- isHumanReadable(decrypt(fileLocation).toByteArray()) -> handleHumanReadableFile(fileLocation, ctx)
- else -> {
- ctx.contentType(Files.probeContentType(Paths.get(fileLocation)))
- ctx.result(decrypt(fileLocation))
- }
- }
- } catch (err: Exception) {
- log.error(err.toString())
- throw NotFoundResponse("Error: File or directory does not exist.")
- }
- }
-
- /**
- * Decrypts a file using the [fileLocation] and the crypto helping class
- */
- private fun decrypt(fileLocation: String): String {
- val cryptoHandler = CryptoHandler(secretKey, "AES/CBC/PKCS5Padding")
- return cryptoHandler.decrypt(fileLocation)
- }
-
- /**
- * Gets directory size recursively
- */
- private fun getDirectorySize(directory: File): Long {
- var length: Long = 0
- for (file in directory.listFiles()!!) {
- length += if (file.isFile) file.length()
- else getDirectorySize(file)
- }
- return length
- }
-
- /**
- * Checks whether the file is binary or human-readable (text)
- */
- private fun isHumanReadable(data: ByteArray): Boolean {
- val text = String(data, Charset.forName("ISO-8859-1"))
- val replacedText = text.replace(
- ("[a-zA-Z0-9ßöäü\\.\\*!\"§\\$\\%&/()=\\?@~'#:,;\\+><\\|\\[\\]\\{\\}\\^°²³\\\\ \\n\\r\\t_\\-`´âêîôÂÊÔÎáéíóàèìòÁÉÍÓÀÈÌÒ©‰¢£¥€±¿»«¼½¾™ª]").toRegex(),
- ""
- )
- val d = (text.length - replacedText.length).toDouble() / text.length.toDouble()
- return d > 0.95
- }
-
- /**
- * Converts bytes to human-readable text like 100MiB
- */
- private fun humanReadableBytes(bytes: Long): String {
- val unit = 1024
- if (bytes < unit) return "$bytes B"
- val exp = (Math.log(bytes.toDouble()) / Math.log(unit.toDouble())).toInt()
- val pre = "KMGTPE"[exp - 1] + "i"
- return String.format("%.1f %sB", bytes / Math.pow(unit.toDouble(), exp.toDouble()), pre)
- }
-
- /**
- * Saves multipart media data into requested directory
- */
- fun upload(ctx: Context) {
- try {
- ctx.uploadedFiles("file").forEach { (_, content, name, _) ->
- val fixedName = name.replace(":", "/") // "fix" for Firefox..
- val userId = userHandler.getVerifiedUserId(ctx)
- val fileLocation = "$fileHome/$userId/$fixedName"
- var addPath = ""
-
- val stringContent = content.bufferedReader(Charsets.UTF_8).use { it.readText() }
-
- fixedName.split("/").forEach {
- addPath += "$it/"
- if (!fixedName.endsWith(it)) databaseController.addFile(addPath, userId, true)
- }
-
- val cryptoHandler = CryptoHandler(secretKey, "AES/CBC/PKCS5Padding")
- cryptoHandler.encrypt(stringContent, fileLocation)
- databaseController.addFile(fixedName, userId, false)
- }
-
- ctx.json("success")
- } catch (err: Exception) {
- log.error(err.toString())
- }
- }
-
- /**
- * Re-indexes every file in the users directory
- */
- fun indexAll(ctx: Context) {
- val userId = userHandler.getVerifiedUserId(ctx)
-
- fun recursiveIndex(filePath: String = "") {
- Files.list(Paths.get("$fileHome/$userId$filePath")).forEach {
- val filename = it.toString().drop("$fileHome/$userId".length + 1)
-
- if (it.toFile().isDirectory) {
- databaseController.addFile("$filename/", userId, true)
- recursiveIndex("/$filename")
- } else databaseController.addFile(filename, userId, false)
- }
- }
-
- recursiveIndex()
- }
-
- /**
- * Deletes the requested file
- */
- fun delete(ctx: Context) {
- val userId = userHandler.getVerifiedUserId(ctx)
- if (userId > 0) {
- val path = ctx.splat(0) ?: ""
- val file = File("$fileHome/$userId/$path")
-
- fun deleteDirectory(recursiveFile: File) {
- val fileList = recursiveFile.listFiles()
- if (fileList != null) {
- for (subFile in fileList) {
- deleteDirectory(subFile)
- }
- }
- recursiveFile.delete()
- }
-
- if (file.isDirectory) {
- deleteDirectory(file)
- } else file.delete()
- databaseController.deleteFile(path, userId)
- }
- }
-
- /**
- * Shares the requested file via the accessId
- */
- fun share(ctx: Context) {
- val userId = userHandler.getVerifiedUserId(ctx)
- val shareType = ctx.queryParam("type").toString()
- val firstParam = ctx.splat(0) ?: ""
- if (userId > 0) {
- val path = "$firstParam${if (shareType == "dir") "/" else ""}"
- val accessId = databaseController.getAccessId(path, userId)
- ctx.result("${ctx.host()}/file/shared?id=$accessId")
- }
- }
-
- /**
- * Renders a shared file
- */
- fun renderShared(ctx: Context) {
- val sharedFileData = databaseController.getSharedFile(ctx.queryParam("id").toString())
- val fileLocation = sharedFileData.fileLocation
- if (sharedFileData.userId > 0 && fileLocation.isNotEmpty()) {
- val sharedFileLocation = "$fileHome/${sharedFileData.userId}/$fileLocation"
- if (!sharedFileData.isDirectory) {
- if (isHumanReadable(decrypt(sharedFileLocation).toByteArray()))
- handleHumanReadableFile(sharedFileLocation, ctx)
- else {
- // TODO: Fix name of downloaded file ("shared")
- ctx.contentType(Files.probeContentType(Paths.get(sharedFileLocation)))
- ctx.result(FileInputStream(File(sharedFileLocation)))
- }
- } else {
- val files = ArrayList<Array<String>>()
- Files.list(Paths.get(sharedFileLocation)).forEach {
- val filename = it.toString()
- .drop(sharedFileLocation.length)
- val filePath = "$sharedFileLocation$filename"
- files.add(addToFileListing(filePath, filename))
- }
- files.sortWith(compareBy { it.first() })
- ctx.render(
- "files.rocker.html", model(
- "files", files,
- "path", (if (fileLocation.firstOrNull() == '/') fileLocation.drop(1) else fileLocation),
- "isShared", true,
- "ctx", ctx
- )
- )
- }
- } else {
- log.warn("Unknown file!")
- throw NotFoundResponse("Shared file couldn't be found.")
- }
- }
-
- /**
- * Adds a file to the file array used in the file listing view
- */
- private fun addToFileListing(fileLocation: String, filename: String): Array<String> {
- val file = File(fileLocation)
- val fileSize = if (file.isDirectory) getDirectorySize(file) else file.length()
- return arrayOf(
- // TODO: Clean up array responses
- if (file.isDirectory) "$filename/" else filename,
- humanReadableBytes(fileSize),
- SimpleDateFormat("MM/dd/yyyy HH:mm:ss").format(file.lastModified()).toString(),
- if (file.isDirectory) "true"
- else isHumanReadable(decrypt(fileLocation).toByteArray()).toString(),
- file.isDirectory.toString(),
- fileSize.toString(), // unformatted file size
- file.lastModified().toString() // unformatted last modified date
- )
- }
-
- /**
- * Handles the rendering of human readable files
- */
- private fun handleHumanReadableFile(fileLocation: String, ctx: Context) {
- val content = decrypt(fileLocation)
- ctx.render(
- "fileview.rocker.html", model(
- "content", content,
- "filename", File(fileLocation).name,
- "extension", File(fileLocation).extension,
- "ctx", ctx
- )
- )
- }
-
- /**
- * Returns the access id of the directory
- */
- fun handleSharedFile(ctx: Context) {
- val filename = ctx.formParam("filename").toString()
- val accessId = ctx.formParam("accessId").toString()
- ctx.result(databaseController.getAccessIdOfDirectory(filename, accessId))
- }
-}
diff --git a/src/main/kotlin/LogFilter.kt b/src/main/kotlin/LogFilter.kt
deleted file mode 100644
index df52f8f..0000000
--- a/src/main/kotlin/LogFilter.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-package space.anity
-
-import ch.qos.logback.classic.*
-import ch.qos.logback.classic.spi.*
-import ch.qos.logback.core.filter.*
-import ch.qos.logback.core.spi.*
-
-class LogFilter : Filter<ILoggingEvent>() {
- override fun decide(event: ILoggingEvent): FilterReply {
- val isUseful = event.loggerName !in
- listOf(
- "Exposed",
- "io.javalin.Javalin",
- "com.fizzed.rocker.runtime.RockerRuntime",
- "org.eclipse.jetty.util.log"
- )
-
- return if (event.level == Level.INFO && isUseful) {
- FilterReply.ACCEPT
- } else {
- if ((!silent || event.message.contains("Help")) && event.level != Level.DEBUG && isUseful) {
- FilterReply.ACCEPT
- } else {
- FilterReply.DENY
- }
- }
- }
-}
diff --git a/src/main/kotlin/UserHandler.kt b/src/main/kotlin/UserHandler.kt
deleted file mode 100644
index 1b0c30a..0000000
--- a/src/main/kotlin/UserHandler.kt
+++ /dev/null
@@ -1,222 +0,0 @@
-package space.anity
-
-import io.javalin.*
-import io.javalin.rendering.template.TemplateUtil.model
-import org.joda.time.*
-import org.slf4j.*
-import kotlin.math.*
-
-class UserHandler {
- private val log = LoggerFactory.getLogger(this.javaClass.name)
-
- /**
- * Renders the login page
- */
- fun renderLogin(ctx: Context) {
- if (userHandler.getVerifiedUserId(ctx) > 0 || !databaseController.isSetup()) ctx.redirect("/")
- else ctx.render("login.rocker.html", model("message", "", "counter", 0, "ctx", ctx))
- }
-
- /**
- * Checks and verifies users credentials and logs the user in
- */
- fun login(ctx: Context) {
- if (getVerifiedUserId(ctx) > 0 || !databaseController.isSetup()) ctx.redirect("/")
-
- val username = ctx.formParam("username").toString()
- val password = ctx.formParam("password").toString()
- val requestIp = ctx.ip()
-
- val loginAttempts = databaseController.getLoginAttempts(requestIp)
- val lastAttemptDifference =
- if (loginAttempts.isEmpty()) -1
- else Interval(
- loginAttempts[loginAttempts.indexOfLast { true }].first.toInstant(),
- Instant()
- ).toDuration().standardSeconds.toInt()
-
- var lastHourAttempts = 0
- loginAttempts.forEach {
- val difference = Interval(it.first.toInstant(), Instant()).toDuration().standardMinutes.toInt()
- if (difference < 60) lastHourAttempts += 1
- }
- val nextThreshold = 4f.pow(lastHourAttempts + 1)
-
- if (lastAttemptDifference > 4f.pow(lastHourAttempts) || lastHourAttempts == 0) {
- if (databaseController.checkUser(username, password)) {
- ctx.cookieStore("verification", databaseController.getVerificationId(username))
- ctx.cookieStore("userId", databaseController.getUserId(username))
- ctx.redirect("/")
- } else {
- databaseController.loginAttempt(DateTime(), requestIp)
- ctx.render(
- "login.rocker.html",
- model(
- "message",
- "Login failed!",
- "counter", if (nextThreshold / 60 > 60) 3600 else nextThreshold.toInt(),
- "ctx", ctx
- )
- )
- }
- } else {
- databaseController.loginAttempt(DateTime(), requestIp)
- ctx.render(
- "login.rocker.html",
- model(
- "message",
- "Too many request.",
- "counter", if (nextThreshold / 60 > 60) 3600 else nextThreshold.toInt(),
- "ctx", ctx
- )
- )
- }
- }
-
- /**
- * Logs the user out of the system
- */
- fun logout(ctx: Context) {
- ctx.clearCookieStore()
- ctx.removeCookie("javalin-cookie-store", "/")
- ctx.redirect("/")
- }
-
- /**
- * Toggles the users dark theme
- */
- fun toggleTheme(ctx: Context) {
- databaseController.toggleDarkTheme(userHandler.getVerifiedUserId(ctx))
- val dark = databaseController.isDarkTheme(userHandler.getVerifiedUserId(ctx))
- ctx.json(mapOf("dark" to dark))
- }
-
- /**
- * Renders the admin interface
- */
- fun renderAdmin(ctx: Context) {
- ctx.render("admin.rocker.html", model("message", "", "ctx", ctx))
- }
-
- /**
- * Renders the setup page
- */
- fun renderSetup(ctx: Context) {
- if (databaseController.isSetup()) ctx.redirect("/user/login")
- else ctx.render("setup.rocker.html", model("message", "", "ctx", ctx))
- }
-
- /**
- * Sets up the general settings and admin credentials
- */
- fun setup(ctx: Context) {
- try {
- val username = ctx.formParam("username").toString()
- val password = ctx.formParam("password").toString()
- val verifyPassword = ctx.formParam("verifyPassword").toString()
-
- // TODO: Clean up ugly if statements in validation
- if (username.matches("[a-zA-Z0-9]+".toRegex()) && username.length > 3) {
- if (password == verifyPassword) {
- if (password.length >= 8)
- if (databaseController.createUser(username, password, "ADMIN")) {
- databaseController.toggleSetup()
- ctx.redirect("/user/login")
- } else ctx.status(400).render(
- "setup.rocker.html",
- model("message", "User already exists!", "ctx", ctx)
- )
- else ctx.status(400).render(
- "setup.rocker.html",
- model("message", "Password is too short!", "ctx", ctx)
- )
- } else ctx.status(400).render(
- "setup.rocker.html",
- model("message", "Passwords do not match!", "ctx", ctx)
- )
- } else ctx.status(400).render(
- "setup.rocker.html",
- model("message", "Username must only use alphabetical characters!", "ctx", ctx)
- )
- } catch (err: Exception) {
- ctx.status(400).render("setup.rocker.html", model("message", "An error occurred!", "ctx", ctx))
- error(err)
- }
- }
-
- /**
- * Renders the registration page
- */
- fun renderRegistration(ctx: Context) {
- val username = ctx.queryParam("username", "")
- val token = ctx.queryParam("token", "")
-
- if (username.isNullOrEmpty()) throw ForbiddenResponse("Please provide a valid username!")
- else if (token.isNullOrEmpty()) throw ForbiddenResponse("Please provide a valid token!")
- else {
- if (databaseController.isUserRegistrationValid(username, token))
- ctx.render(
- "register.rocker.html",
- model("username", username, "token", token, "message", "", "ctx", ctx)
- )
- else ctx.redirect("/user/login")
- }
- }
-
- /**
- * Registers a new user
- */
- fun register(ctx: Context) {
- try {
- val username = ctx.formParam("username").toString()
- val token = ctx.formParam("token").toString()
- val password = ctx.formParam("password").toString()
- val verifyPassword = ctx.formParam("verifyPassword").toString()
-
- if (password == verifyPassword) {
- if (password.length >= 8)
- if (databaseController.isUserRegistrationValid(username, token)) {
- databaseController.createUser(username, password, "USER")
- databaseController.removeRegistrationIndex(username)
- ctx.redirect("/user/login")
- } else ctx.render(
- "register.rocker.html",
- model("username", username, "token", token, "message", "Not authorized!", "ctx", ctx)
- )
- else ctx.render(
- "register.rocker.html",
- model(
- "username", username,
- "token", token,
- "message", "Please make sure that your password is at least 8 digits long!",
- "ctx", ctx
- )
- )
- } else ctx.render(
- "register.rocker.html",
- model("username", username, "token", token, "message", "The passwords don't match!", "ctx", ctx)
- )
- } catch (err: Exception) {
- throw BadRequestResponse()
- }
- }
-
- /**
- * Gets the username and verifies its identity
- */
- fun getVerifiedUserId(ctx: Context): Int {
- return if (databaseController.getUserIdByVerificationId(ctx.cookieStore("verification") ?: "verification")
- == ctx.cookieStore("userId") ?: "userId"
- ) ctx.cookieStore("userId")
- else -1
- }
-
- /**
- * Checks whether a user has admin privileges
- */
- fun isAdmin(usernameString: String): Boolean {
- val userId = databaseController.getUserId(usernameString)
- return if (userId > 0) databaseController.getRoles(userId).contains(Roles.ADMIN)
- else false
- }
-}