...
 
Commits (5)
......@@ -121,3 +121,13 @@ CREATE TABLE global_motds (
audience VARCHAR(255) NOT NULL,
PRIMARY KEY(id)
) ENGINE=INNODB;
CREATE TABLE login_tokens (
token VARCHAR(255) NOT NULL,
user_id INT NOT NULL,
created VARCHAR(30) NOT NULL,
modified VARCHAR(30),
last_used VARCHAR(30) NOT NULL,
PRIMARY KEY(token),
CONSTRAINT login_token_user_fk FOREIGN KEY (user_id) REFERENCES users(id) ON UPDATE CASCADE ON DELETE CASCADE
) ENGINE=INNODB;
......@@ -4,16 +4,17 @@ import akka.http.scaladsl.server.Directives._
import de.thm.arsnova.api._
trait Routes extends ApiErrorHandler with UserApi with SessionApi with QuestionApi with FreetextAnswerApi with ChoiceAnswerApi
with CommentApi with FeaturesApi with SessionMotdApi with GlobalMotdApi {
with CommentApi with FeaturesApi with SessionMotdApi with GlobalMotdApi with AuthApi {
val routes = {
userApi ~
sessionApi ~
questionApi ~
freetextAnswerApi ~
choiceAnswerApi ~
commentApi ~
featuresApi ~
sessionMotdApi ~
globalMotdApi
} ~ path("")(getFromResource("public/index.html"))
userApi ~
sessionApi ~
questionApi ~
freetextAnswerApi ~
choiceAnswerApi ~
commentApi ~
featuresApi ~
sessionMotdApi ~
globalMotdApi ~
authApi
} ~ path("")(getFromResource("public/index.html"))
}
package de.thm.arsnova.api
import de.thm.arsnova.services.AuthService
import de.thm.arsnova.models._
import de.thm.arsnova.hateoas.{ApiRoutes, ResourceAdapter, Link}
import scala.concurrent.ExecutionContext.Implicits.global
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._
import akka.http.scaladsl.server.Directives._
import spray.json._
/*
The API Interface regarding user auth.
*/
trait AuthApi {
// protocol for serializing data
import de.thm.arsnova.mappings.LoginTokenJsonProtocol._
// add the "top level" endpoint to ApiRoutes
ApiRoutes.addRoute("auth", "auth")
val authApi = pathPrefix(ApiRoutes.getRoute("auth")) {
get {
parameters("username".as[String], "password".as[String]) { (username, password) =>
complete (AuthService.login(username, password).map(_.toJson))
}
}
}
}
......@@ -14,9 +14,6 @@ trait UserApi {
val userApi = pathPrefix("") {
pathEndOrSingleSlash {
get {
complete (UserService.findAll)
} ~
post {
entity(as[User]) { user =>
complete (UserService.create(user).map(_.toJson))
......
package de.thm.arsnova.mappings
import de.thm.arsnova.models.LoginToken
import spray.json.{DefaultJsonProtocol, RootJsonFormat}
object LoginTokenJsonProtocol extends DefaultJsonProtocol {
implicit val loginTokenFormat: RootJsonFormat[LoginToken] = jsonFormat5(LoginToken)
}
\ No newline at end of file
package de.thm.arsnova.models
case class LoginToken(token: String, userId: Long, created: String, modified: Option[String], lastUsed: String)
package de.thm.arsnova.models.definitions
import de.thm.arsnova.models.LoginToken
import slick.driver.MySQLDriver.api._
class LoginTokensTable(tag: Tag) extends Table[LoginToken](tag, "login_tokens") {
def token: Rep[String] = column[String]("token", O.PrimaryKey, O.AutoInc)
def userId: Rep[Long] = column[Long]("user_id")
def created: Rep[String] = column[String]("created")
def modified: Rep[String] = column[String]("modified")
def lastUsed: Rep[String] = column[String]("last_used")
def * = (token, userId, created, modified.?, lastUsed) <> ((LoginToken.apply _).tupled, LoginToken.unapply)
}
\ No newline at end of file
package de.thm.arsnova.security
import de.thm.arsnova.models.User
import de.thm.arsnova.services.UserService
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
import akka.http.scaladsl.model.StatusCodes.Unauthorized
import akka.http.scaladsl.server.{Directive1, Directives}
import spray.json.{JsObject, JsString}
object Authentication extends Directives with SprayJsonSupport {
val authenticate: Directive1[User] = {
optionalHeaderValueByName("Authorization") flatMap {
case Some(authHeader) =>
val accessToken = authHeader.split(' ').last
onSuccess(UserService.getByLoginTokenString(accessToken)).flatMap {
case user: User => provide(user)
case _ => complete(Unauthorized, JsObject(Map("status" -> JsString("Wrong Authorization header"))))
}
case _ => complete(Unauthorized, JsObject(Map("status" -> JsString("Missing Authorization header"))))
}
}
}
package de.thm.arsnova.services
import slick.driver.MySQLDriver.api._
import de.thm.arsnova.models.{LoginToken, User}
import scala.concurrent.Await
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
object AuthService extends BaseService {
def login(username: String, password: String): Future[Option[LoginToken]] = {
db.run(usersTable.filter((user) => user.username === username && user.password === password).result.head).map {
case u: User => Some(Await.result(LoginTokenService.create(u), 5.seconds))
case _ => None
}
}
}
......@@ -21,6 +21,7 @@ trait BaseService extends DatabaseConfig {
val featuresTable = TableQuery[FeaturesTable]
val globalMotdsTable = TableQuery[GlobalMotdsTable]
val sessionMotdsTable = TableQuery[SessionMotdsTable]
val loginTokensTable = TableQuery[LoginTokensTable]
protected implicit def executeFromDb[A](action: SqlAction[A, NoStream, _ <: slick.dbio.Effect]): Future[A] = {
db.run(action)
......
package de.thm.arsnova.services
import slick.driver.MySQLDriver.api._
import de.thm.arsnova.models.{LoginToken, User}
import scala.concurrent.Future
import java.util.Date
import java.util.UUID.randomUUID
object LoginTokenService extends BaseService {
def create(user: User): Future[LoginToken] = {
val now = new Date().getTime
val token = LoginToken(randomUUID.toString, user.id.get, now.toString, None, now.toString)
loginTokensTable.returning(loginTokensTable) += token
}
}
package de.thm.arsnova.services
import de.thm.arsnova.models.{UserId, User}
import de.thm.arsnova.models.{User, UserId}
import slick.driver.MySQLDriver.api._
import scala.concurrent.Future
object UserService extends BaseService{
def findAll: Future[Seq[User]] = usersTable.result
def findById(userId: UserId): Future[User] = usersTable.filter(_.id === userId).result.head
def create(user: User): Future[UserId] = usersTable returning usersTable.map(_.id) += user
def update(newUser: User, userId: UserId): Future[Int] = usersTable.filter(_.id === userId)
.map(user => (user.username, user.password))
.update((newUser.userName, newUser.password))
object UserService extends BaseService {
def findById(userId: UserId): Future[User] = {
usersTable.filter(_.id === userId).result.head
}
def create(user: User): Future[UserId] = {
usersTable returning usersTable.map(_.id) += user
}
def update(newUser: User, userId: UserId): Future[Int] = {
usersTable.filter(_.id === userId)
.map(user => (user.username, user.password))
.update((newUser.userName, newUser.password))
}
def delete(userId: UserId): Future[Int] = {
usersTable.filter(_.id === userId).delete
}
def delete(userId: UserId): Future[Int] = usersTable.filter(_.id === userId).delete
def getByLoginTokenString(loginTokenString: String): Future[User] = {
val qry = for {
token <- loginTokensTable filter(_.token === loginTokenString)
user <- usersTable if (token.userId === user.id)
} yield (user)
db.run(qry.result.head)
}
}