aboutsummaryrefslogtreecommitdiff
path: root/src/main/scala/byspel/Ui.scala
blob: ac26164df8aeccf7d74f2127bb8c119938b5806c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
package byspel

import akka.http.scaladsl.server.Route
import akka.http.scaladsl.marshalling.{Marshaller, ToEntityMarshaller}
import akka.http.scaladsl.model.headers.HttpCookie
import akka.http.scaladsl.model.{MediaTypes, StatusCodes, Uri}
import app.HttpApi
import scalatags.Text.all._

trait Ui extends HttpApi { self: Service with Tables =>

  // allows using scalatags templates as HTTP responses
  implicit val tagMarshaller: ToEntityMarshaller[Tag] = {
    Marshaller.stringMarshaller(MediaTypes.`text/html`).compose { (tag: Tag) =>
      tag.render
    }
  }

  def page(content: Tag*) = html(
    scalatags.Text.all.head(
      link(
        rel := "stylesheet",
        `type` := "text/css",
        href := "/assets/normalize.css"
      ),
      link(
        rel := "stylesheet",
        `type` := "text/css",
        href := "/assets/main.css"
      )
    ),
    body(
      content
    )
  )

  def loginForm(alert: Option[String]) = page(
    img(src := "/assets/logo.svg"),
    h3("Sign in to crashbox"),
    alert match {
      case Some(message) => div(`class` := "alert")(message)
      case None          => span()
    },
    form(action := "/login", attr("method") := "post")(
      label(`for` := "username")("Username or email address"),
      input(`type` := "text", placeholder := "", name := "username", required),
      label(`for` := "password")("Password"),
      input(`type` := "password",
            placeholder := "",
            name := "password",
            required),
      button(`type` := "submit")("Sign in")
    )
  )

  def mainPage(user: UsersRow) = page(
    h1(s"Welcome ${user.fullName.getOrElse("")}!"),
    form(action := "/logout", attr("method") := "post")(
      button(`type` := "submit")("Sign out")
    )
  )

  def authenticated(inner: UsersRow => Route): Route =
    optionalCookie("session") {
      case Some(sessionCookie) =>
        onSuccess(self.checkSession(sessionCookie.value)) {
          case Some(user) =>
            inner(user)
          case None => complete(StatusCodes.NotFound)
        }
      case None => complete(StatusCodes.NotFound)
    }

  def route =
    pathPrefix("assets") {
      getFromResourceDirectory("assets")
    } ~ path("login") {
      get {
        complete(loginForm(None))
      } ~
        post {
          formFields("username", "password") {
            case (u, p) =>
              onSuccess(self.login(u, p)) {
                case None =>
                  complete(StatusCodes.NotFound -> loginForm(
                    Some("Incorrect username or password.")))
                case Some((user, session)) =>
                  setCookie(HttpCookie("session", session.sessionId)) {
                    redirect(Uri(s"/${user.primaryEmail}"), StatusCodes.Found)
                  }
              }
          }
        }
    } ~ path("logout") {
      post {
        cookie("session") { cookiePair =>
          onSuccess(endSession(cookiePair.value)) { _ =>
            deleteCookie(cookiePair.name) {
              redirect(Uri("/"), StatusCodes.Found)
            }
          }
        }
      }
    } ~ path(Segment) { userEmail =>
      authenticated { user =>
        if (user.primaryEmail == userEmail) {
          get {
            complete(mainPage(user))
          }
        } else {
          complete(StatusCodes.NotFound)
        }
      }
    } ~ get {
      redirect(Uri("/login"), StatusCodes.Found)
    }

}