base frontend website with authentication

This commit is contained in:
Jaromil 2018-10-03 00:00:07 +02:00
parent 507e941540
commit 4c033ac86c
21 changed files with 1358 additions and 0 deletions

View File

@ -0,0 +1,45 @@
(defproject toaster "0.1.0-SNAPSHOT"
:description "Basic compojure based authenticated website"
:url "http://dyne.org"
:min-lein-version "2.0.0"
:dependencies [[org.clojure/clojure "1.9.0"]
[org.clojure/data.csv "0.1.4"]
[compojure "1.6.1"]
[ring/ring-defaults "0.3.2"]
[ring-middleware-accept "2.0.3"]
;; mustache templates
[de.ubercode.clostache/clostache "1.4.0"]
;; error handling
[failjure "1.3.0"]
;; logging done right with timbre
[com.taoensso/timbre "4.10.0"]
;; authentication library
[org.clojars.dyne/just-auth "0.4.0"]
;; web forms made easy
[formidable "0.1.10"]
;; parsing configs if any
[io.forward/yaml "1.0.9"]
;; Data validation
[prismatic/schema "1.1.9"]
;; filesystem utilities
[me.raynes/fs "1.4.6"]
;; time from joda-time
[clj-time "0.14.4"]]
:aliases {"test" "midje"}
:source-paths ["src"]
:resource-paths ["resources"]
:plugins [[lein-ring "0.12.4"]]
:ring {:init toaster.ring/init
:handler toaster.handler/app}
:uberwar {:init toaster.ring/init
:handler toaster.handler/app}
:mail toaster.handler
:profiles { :dev {:dependencies [[javax.servlet/servlet-api "2.5"]
[ring/ring-mock "0.3.2"]
[midje "1.9.2"]]
:plugins [[lein-midje "3.1.3"]]
:aot :all
:main toaster.handler}
:uberjar {:aot :all
:main toaster.handler}}
)

View File

@ -0,0 +1,6 @@
---
init:
success: "Initialisation completed"
failure: "Initialization failed"

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,5 @@
/*!
* Font Awesome Free 5.0.6 by @fontawesome - http://fontawesome.com
* License - http://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
*/
@font-face{font-family:Font Awesome\ 5 Free;font-style:normal;font-weight:400;src:url(/static/fonts/fa-regular-400.eot);src:url(/static/fonts/fa-regular-400.eot?#iefix) format("embedded-opentype"),url(/static/fonts/fa-regular-400.woff2) format("woff2"),url(/static/fonts/fa-regular-400.woff) format("woff"),url(/static/fonts/fa-regular-400.ttf) format("truetype"),url(/static/fonts/fa-regular-400.svg#fontawesome) format("svg")}.far{font-family:Font Awesome\ 5 Free;font-weight:400}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,46 @@
.jsondiffpatch-annotated-delta {
font-family: 'Bitstream Vera Sans Mono', 'DejaVu Sans Mono', Monaco, Courier, monospace;
font-size: 12px;
margin: 0;
padding: 0 0 0 12px;
display: inline-block;
}
.jsondiffpatch-annotated-delta pre {
font-family: 'Bitstream Vera Sans Mono', 'DejaVu Sans Mono', Monaco, Courier, monospace;
font-size: 12px;
margin: 0;
padding: 0;
display: inline-block;
}
.jsondiffpatch-annotated-delta td {
margin: 0;
padding: 0;
}
.jsondiffpatch-annotated-delta td pre:hover {
font-weight: bold;
}
td.jsondiffpatch-delta-note {
font-style: italic;
padding-left: 10px;
}
.jsondiffpatch-delta-note > div {
margin: 0;
padding: 0;
}
.jsondiffpatch-delta-note pre {
font-style: normal;
}
.jsondiffpatch-annotated-delta .jsondiffpatch-delta-note {
color: #777;
}
.jsondiffpatch-annotated-delta tr:hover {
background: #ffc;
}
.jsondiffpatch-annotated-delta tr:hover > td.jsondiffpatch-delta-note {
color: black;
}
.jsondiffpatch-error {
background: red;
color: white;
font-weight: bold;
}

View File

@ -0,0 +1,149 @@
.jsondiffpatch-delta {
font-family: 'Bitstream Vera Sans Mono', 'DejaVu Sans Mono', Monaco, Courier, monospace;
font-size: 12px;
margin: 0;
padding: 0 0 0 12px;
display: inline-block;
}
.jsondiffpatch-delta pre {
font-family: 'Bitstream Vera Sans Mono', 'DejaVu Sans Mono', Monaco, Courier, monospace;
font-size: 12px;
margin: 0;
padding: 0;
display: inline-block;
}
ul.jsondiffpatch-delta {
list-style-type: none;
padding: 0 0 0 20px;
margin: 0;
}
.jsondiffpatch-delta ul {
list-style-type: none;
padding: 0 0 0 20px;
margin: 0;
}
.jsondiffpatch-added .jsondiffpatch-property-name,
.jsondiffpatch-added .jsondiffpatch-value pre,
.jsondiffpatch-modified .jsondiffpatch-right-value pre,
.jsondiffpatch-textdiff-added {
background: #bbffbb;
}
.jsondiffpatch-deleted .jsondiffpatch-property-name,
.jsondiffpatch-deleted pre,
.jsondiffpatch-modified .jsondiffpatch-left-value pre,
.jsondiffpatch-textdiff-deleted {
background: #ffbbbb;
text-decoration: line-through;
}
.jsondiffpatch-unchanged,
.jsondiffpatch-movedestination {
color: gray;
}
.jsondiffpatch-unchanged,
.jsondiffpatch-movedestination > .jsondiffpatch-value {
transition: all 0.5s;
-webkit-transition: all 0.5s;
overflow-y: hidden;
}
.jsondiffpatch-unchanged-showing .jsondiffpatch-unchanged,
.jsondiffpatch-unchanged-showing .jsondiffpatch-movedestination > .jsondiffpatch-value {
max-height: 100px;
}
.jsondiffpatch-unchanged-hidden .jsondiffpatch-unchanged,
.jsondiffpatch-unchanged-hidden .jsondiffpatch-movedestination > .jsondiffpatch-value {
max-height: 0;
}
.jsondiffpatch-unchanged-hiding .jsondiffpatch-movedestination > .jsondiffpatch-value,
.jsondiffpatch-unchanged-hidden .jsondiffpatch-movedestination > .jsondiffpatch-value {
display: block;
}
.jsondiffpatch-unchanged-visible .jsondiffpatch-unchanged,
.jsondiffpatch-unchanged-visible .jsondiffpatch-movedestination > .jsondiffpatch-value {
max-height: 100px;
}
.jsondiffpatch-unchanged-hiding .jsondiffpatch-unchanged,
.jsondiffpatch-unchanged-hiding .jsondiffpatch-movedestination > .jsondiffpatch-value {
max-height: 0;
}
.jsondiffpatch-unchanged-showing .jsondiffpatch-arrow,
.jsondiffpatch-unchanged-hiding .jsondiffpatch-arrow {
display: none;
}
.jsondiffpatch-value {
display: inline-block;
}
.jsondiffpatch-property-name {
display: inline-block;
padding-right: 5px;
vertical-align: top;
}
.jsondiffpatch-property-name:after {
content: ': ';
}
.jsondiffpatch-child-node-type-array > .jsondiffpatch-property-name:after {
content: ': [';
}
.jsondiffpatch-child-node-type-array:after {
content: '],';
}
div.jsondiffpatch-child-node-type-array:before {
content: '[';
}
div.jsondiffpatch-child-node-type-array:after {
content: ']';
}
.jsondiffpatch-child-node-type-object > .jsondiffpatch-property-name:after {
content: ': {';
}
.jsondiffpatch-child-node-type-object:after {
content: '},';
}
div.jsondiffpatch-child-node-type-object:before {
content: '{';
}
div.jsondiffpatch-child-node-type-object:after {
content: '}';
}
.jsondiffpatch-value pre:after {
content: ',';
}
li:last-child > .jsondiffpatch-value pre:after,
.jsondiffpatch-modified > .jsondiffpatch-left-value pre:after {
content: '';
}
.jsondiffpatch-modified .jsondiffpatch-value {
display: inline-block;
}
.jsondiffpatch-modified .jsondiffpatch-right-value {
margin-left: 5px;
}
.jsondiffpatch-moved .jsondiffpatch-value {
display: none;
}
.jsondiffpatch-moved .jsondiffpatch-moved-destination {
display: inline-block;
background: #ffffbb;
color: #888;
}
.jsondiffpatch-moved .jsondiffpatch-moved-destination:before {
content: ' => ';
}
ul.jsondiffpatch-textdiff {
padding: 0;
}
.jsondiffpatch-textdiff-location {
color: #bbb;
display: inline-block;
min-width: 60px;
}
.jsondiffpatch-textdiff-line {
display: inline-block;
}
.jsondiffpatch-textdiff-line-number:after {
content: ',';
}
.jsondiffpatch-error {
background: red;
color: white;
font-weight: bold;
}

View File

@ -0,0 +1,72 @@
/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */
/* Tomorrow Comment */
.hljs-comment,
.hljs-quote {
color: #8e908c;
}
/* Tomorrow Red */
.hljs-variable,
.hljs-template-variable,
.hljs-tag,
.hljs-name,
.hljs-selector-id,
.hljs-selector-class,
.hljs-regexp,
.hljs-deletion {
color: #c82829;
}
/* Tomorrow Orange */
.hljs-number,
.hljs-built_in,
.hljs-builtin-name,
.hljs-literal,
.hljs-type,
.hljs-params,
.hljs-meta,
.hljs-link {
color: #f5871f;
}
/* Tomorrow Yellow */
.hljs-attribute {
color: #eab700;
}
/* Tomorrow Green */
.hljs-string,
.hljs-symbol,
.hljs-bullet,
.hljs-addition {
color: #718c00;
}
/* Tomorrow Blue */
.hljs-title,
.hljs-section {
color: #4271ae;
}
/* Tomorrow Purple */
.hljs-keyword,
.hljs-selector-tag {
color: #8959a8;
}
.hljs {
display: block;
overflow-x: auto;
background: white;
color: #4d4d4c;
padding: 0.5em;
}
.hljs-emphasis {
font-style: italic;
}
.hljs-strong {
font-weight: bold;
}

View File

@ -0,0 +1,110 @@
/* css for [json-html.core] */
.jh-type-string, .jh-type-bool, .jh-type-number{
font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
font-size: 14px;
font-weight: normal;
line-height: 20px;
color: #333;
float: left;
width: 140px;
padding-top: 5px;
text-align: right;
margin-bottom: 10px;
}
.jh-root, .jh-type-object, .jh-type-array, .jh-key, .jh-value, .jh-root tr{
-webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */
-moz-box-sizing: border-box; /* Firefox, other Gecko */
box-sizing: border-box; /* Opera/IE 8+ */
}
.jh-key, .jh-value{
margin: 0;
padding: 0.2em;
}
.jh-empty-collection:before {
content: '[]';
}
.jh-empty-map:before {
content: '{}';
}
.jh-empty-set:before {
content: '#{}';
}
.jh-empty-string:before {
content: '""';
}
.jh-value{
border-left: 1px solid #ddd;
}
.jh-type-bool, .jh-type-number{
font-weight: bold;
color: #333;
}
.jh-type-string{
color: #333;
}
.jh-type-date{
color: #333;
}
.jh-array-key{
font-size: small;
text-align: center;
}
.jh-object-key, .jh-array-key{
color: #444;
vertical-align: top;
}
.jh-type-object > tbody > tr:nth-child(odd), .jh-type-array > tbody > tr:nth-child(odd){
background-color: #f5f5f5;
}
.jh-type-object > tbody > tr:nth-child(even), .jh-type-array > tbody > tr:nth-child(even){
background-color: #fff;
}
.jh-type-object, .jh-type-array{
width: 100%;
border-collapse: collapse;
}
.jh-root{
border: 1px solid #ccc;
margin: 0.2em;
}
th.jh-key{
text-align: left;
}
.jh-type-object > tbody > tr, .jh-type-array > tr{
border: 1px solid #ddd;
border-bottom: none;
}
.jh-type-object > tbody > tr:last-child, .jh-type-array > tbody > tr:last-child{
border-bottom: 1px solid #ddd;
}
.jh-type-object > tbody > tr:hover, .jh-type-array > tbody > tr:hover{
border: 1px solid #F99927;
}
.jh-empty{
color: #999;
font-size: small;
}

View File

@ -0,0 +1,123 @@
body {
font-family: "arial";
font-size: 22px;
font-smooth: always;
-webkit-font-smoothing: antialiased;
padding-top: 65px;
}
code {
font-size: .8em;
/* display: block; */
white-space: pre-wrap;
}
.editor {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
/* .btn { */
/* margin: 0 1.5em 0 1.5em; */
/* } */
.edit-project {
margin-top: 2em;
margin-bottom: 2em;
}
h1, h2, h3, h4 { }
p {
letter-spacing: 0.01rem;
font-style: normal;
line-height: 1.5;
}
img {
width: auto;
height: auto;
max-width: 100%;
vertical-align: middle;
overflow: hidden;
}
/* Sortable tables */
table.sortable thead {
background-color:#eee;
color:#666666;
font-weight: bold;
cursor: default;
}
table.sortable td {
font-size: .8em;
font-family: "arial"
}
.input-form {
clear: right;
}
li {
/* padding: .5em; */
}
.secrets {
padding: 1em;
}
.password {
font-size: 1.5;
padding: 1em;
/* display: inline-block; */
}
.navbar-text {
margin-top: 22px;
}
.password .content {
font-size: 1em;
}
.slices {
}
.slices ul {
list-style: none;
}
.slices .content {
font-size: .5em;
}
.qrcode {
float: left;
margin-left: -19px;
}
.card {
border: solid 3px #888;
border-radius: 5px;
padding: 1.5em 2.2em .8em 2.2em;
}
.card .gravatar {
margin-top: 19px;
}
.balance {
font-size: 2em;
margin: 1em 0;
}
.wallet-details {
display: inline-block;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,101 @@
;; Copyright (C) 2015-2018 Dyne.org foundation
;; Sourcecode designed, written and maintained by
;; Denis Roio <jaromil@dyne.org>
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU Affero General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU Affero General Public License for more details.
;; You should have received a copy of the GNU Affero General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
(ns toaster.config
(:require [clojure.pprint :refer [pprint]]
[clojure.string :refer [upper-case]]
[clojure.java.io :as io]
[clojure.walk :refer [keywordize-keys]]
[auxiliary.core :as aux]
[taoensso.timbre :as log]
[failjure.core :as f]
[schema.core :as s]
[yaml.core :as yaml]
[cheshire.core :refer :all]))
;; (s/defschema Config
;; {s/Keyword
;; (s/optional-key :webserver) {:port s/Num
;; :host s/Str
;; :anti-forgery s/Bool
;; :ssl-redirect s/Bool}
;; (s/optional-key :just-auth) {:email-server s/Str
;; :email-user s/Str
;; :email-pass s/Str
;; :email-address s/Str}
;; }
;; :appname s/Str
;; :paths [s/Str]
;; :filename s/Str
;; })
(def run-mode (atom :web))
(def default-settings {:webserver
{:anti-forgery false
:ssl-redirect false}})
(defn yaml-read [path]
(if (.exists (io/as-file path))
(-> path yaml/from-file keywordize-keys)))
(defn- config-read
"Read configurations from standard locations, overriding defaults or
system-wide with user specific paths. Requires the application name
and optionally default values."
([appname] (config-read appname {}))
([appname defaults & flags]
(let [home (System/getenv "HOME")
pwd (System/getenv "PWD" )
file (str appname ".yaml")
conf (-> {:appname appname
:filename file
:paths [(str "/etc/" appname "/" file)
(str home "/." appname "/" file)
(str pwd "/" file)
;; TODO: this should be resources
(str pwd "/resources/" file)
(str pwd "/test-resources/" file)]}
(conj defaults))]
(loop [[p & paths] (:paths conf)
res defaults]
(let [res (merge res
(if (.exists (io/as-file p))
(conj res (yaml-read p))))]
(if (empty? paths) (conj conf {(keyword appname) res})
(recur paths res)))))))
(defn- spy "Print out a config structure nicely formatted"
[edn]
(if (log/may-log? :debug)
(binding [*out* *err*] (pprint edn)))
edn)
(defn q [conf path] ;; query a variable inside the config
{:pre [(coll? path)]}
;; (try ;; adds an extra check every time configuration is read
;; (s/validate Config conf)
;; (catch Exception ex
;; (f/fail (log/spy :error ["Invalid configuration: " conf ex]))))
(get-in conf path))
(defn load-config [name default]
(log/info (str "Loading configuration: " name))
(->> (config-read name default)))

View File

@ -0,0 +1,167 @@
;; Copyright (C) 2015-2018 Dyne.org foundation
;; Sourcecode designed, written and maintained by
;; Denis Roio <jaromil@dyne.org>
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU Affero General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU Affero General Public License for more details.
;; You should have received a copy of the GNU Affero General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
(ns toaster.handler
(:require
[clojure.string :as str]
[clojure.java.io :as io]
[clojure.data.json :as json]
[compojure.core :refer :all]
[compojure.handler :refer :all]
[compojure.route :as route]
[compojure.response :as response]
[ring.adapter.jetty :refer :all]
[ring.middleware.session :refer :all]
[ring.middleware.accept :refer [wrap-accept]]
[ring.middleware.defaults :refer [wrap-defaults site-defaults]]
[failjure.core :as f]
[taoensso.timbre :as log]
[just-auth.core :as auth]
[toaster.session :as s]
[toaster.config :as conf]
[toaster.webpage :as web]
[toaster.ring :as ring])
(:import java.io.File)
(:gen-class))
(defonce config (conf/load-config "toaster" conf/default-settings))
(defroutes app-routes
(GET "/" request (web/render "Hello World!"));; web/readme))
;; NEW ROUTES HERE
;; JUST-AUTH ROUTES
(GET "/login" request
(f/attempt-all
[acct (s/check-account request)]
(web/render acct
[:div
[:h1 (str "Already logged in with account: "
(:email acct))]
[:h2 [:a {:href "/logout"} "Logout"]]])
(f/when-failed [e]
(web/render web/login-form))))
(POST "/login" request
(f/attempt-all
[username (s/param request :username)
password (s/param request :password)
logged (auth/sign-in
@ring/auth username password {})]
;; TODO: pass :ip-address in last argument map
(let [session {:session {:config config
:auth logged}}]
(conj session
(web/render
logged
[:div
[:h1 "Logged in: " username]
(web/render-yaml session)])))
(f/when-failed [e]
(web/render-error-page
(str "Login failed: " (f/message e))))))
(GET "/session" request
(-> (:session request) web/render-yaml web/render))
(GET "/logout" request
(conj {:session {:config config}}
(web/render [:h1 "Logged out."])))
(GET "/signup" request
(web/render web/signup-form))
(POST "/signup" request
(f/attempt-all
[name (s/param request :name)
email (s/param request :email)
password (s/param request :password)
repeat-password (s/param request :repeat-password)
activation {:activation-uri
(get-in request [:headers "host"])}]
(web/render
(if (= password repeat-password)
(f/try*
(f/if-let-ok?
[signup (auth/sign-up @ring/auth
name
email
password
activation
[])]
[:div
[:h2 (str "Account created: "
name " &lt;" email "&gt;")]
[:h3 "Account pending activation."]]
(web/render-error
(str "Failure creating account: "
(f/message signup)))))
(web/render-error
"Repeat password didnt match")))
(f/when-failed [e]
(web/render-error-page
(str "Sign-up failure: " (f/message e))))))
(GET "/activate/:email/:activation-id"
[email activation-id :as request]
(let [activation-uri
(str "http://"
(get-in request [:headers "host"])
"/activate/" email "/" activation-id)]
(web/render
[:div
(f/if-let-failed?
[act (auth/activate-account
@ring/auth email
{:activation-link activation-uri})]
(web/render-error
[:div
[:h1 "Failure activating account"]
[:h2 (f/message act)]
[:p (str "Email: " email " activation-id: " activation-id)]])
[:h1 (str "Account activated - " email)])])))
;; -- end of JUST-AUTH
(POST "/" request
;; generic endpoint for canceled operations
(web/render (s/check-account request)
[:div {:class (str "alert alert-danger") :role "alert"}
(s/param request :message)]))
(route/resources "/")
(route/not-found (web/render-error-page "Page Not Found"))
) ;; end of routes
(def app
(-> (wrap-defaults app-routes ring/app-defaults)
(wrap-accept {:mime ["text/html"]
;; preference in language, fallback to english
:language ["en" :qs 0.5
"it" :qs 1
"nl" :qs 1
"hr" :qs 1]})
(wrap-session)))
;; for uberjar
(defn -main []
(println "Starting standalone jetty server on http://localhost:6060")
(run-jetty app {:port 6060
:host "localhost"
:join? true}))

View File

@ -0,0 +1,93 @@
;; Copyright (C) 2015-2017 Dyne.org foundation
;; Sourcecode designed, written and maintained by
;; Denis Roio <jaromil@dyne.org>
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU Affero General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU Affero General Public License for more details.
;; You should have received a copy of the GNU Affero General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
(ns toaster.ring
(:require
[clojure.java.io :as io]
[toaster.config :as conf]
[taoensso.timbre :as log]
[failjure.core :as f]
[clj-storage.db.mongo :refer [get-mongo-db create-mongo-store]]
[just-auth.core :as auth]
[just-auth.db.just-auth :as auth-db]
[auxiliary.translation :as trans]
[compojure.core :refer :all]
[compojure.handler :refer :all]
[ring.middleware.session :refer :all]
[ring.middleware.accept :refer [wrap-accept]]
[ring.middleware.defaults :refer [wrap-defaults site-defaults]]))
(def config (atom {}))
(def db (atom {}))
(def accts (atom {}))
(def auth (atom {}))
(defn init []
(log/merge-config! {:level :debug
;; #{:trace :debug :info :warn :error :fatal :report}
;; Control log filtering by
;; namespaces/patterns. Useful for turning off
;; logging in noisy libraries, etc.:
;; :ns-whitelist ["agiladmin.*" "just-auth.*"]
:ns-blacklist ["org.eclipse.jetty.*"
"org.mongodb.driver.cluster"]})
;; load configuration
(reset! config (conf/load-config
(or (System/getenv "toaster_conf") "toaster")
conf/default-settings))
(let [justauth-conf (get-in @config [:toaster :just-auth])]
;; connect database (TODO: take parameters from configuration)
(reset! db (get-mongo-db (:mongo-url justauth-conf)))
;; create authentication stores in db
(f/attempt-all
[auth-conf (get-in @config [:toaster :just-auth])
auth-stores (auth-db/create-auth-stores @db)]
[(trans/init "lang/auth-en.yml" "lang/english.yaml")
(reset! accts auth-stores)
(reset! auth (auth/email-based-authentication
auth-stores
;; TODO: replace with email taken from config
(dissoc (:just-auth (:toaster (conf/load-config
"toaster" conf/default-settings)))
:mongo-url :mongo-user :mongo-pass)
{:criteria #{:email :ip-address}
:type :block
:time-window-secs 10
:threshold 5}))]
;; (select-keys auth-stores [:account-store
;; :password-recovery-store])
(f/when-failed [e]
(log/error (str (trans/locale [:init :failure])
" - " (f/message e))))))
(log/info (str (trans/locale [:init :success])))
(log/debug @auth))
(def app-defaults
(-> site-defaults
(assoc-in [:cookies] true)
(assoc-in [:security :anti-forgery]
(get-in @config [:webserver :anti-forgery]))
(assoc-in [:security :ssl-redirect]
(get-in @config [:webserver :ssl-redirect]))
(assoc-in [:security :hsts] true)))

View File

@ -0,0 +1,60 @@
(ns toaster.session
(:refer-clojure :exclude [get])
(:require
[toaster.config :as conf]
[taoensso.timbre :as log]
[failjure.core :as f]
[just-auth.core :as auth]
[toaster.ring :as ring]
[toaster.webpage :as web]))
(defn param [request param]
(let [value
(get-in request
(conj [:params] param))]
(if (nil? value)
(f/fail (str "Parameter not found: " param))
value)))
;; TODO: not working?
(defn get [req arrk]
{:pre (coll? arrk)}
(if-let [value (get-in req (conj [:session] arrk))]
value
(f/fail (str "Value not found in session: " (str arrk)))))
(defn check-config [request]
;; reload configuration from file all the time if in debug mode
(if-let [session (:session request)]
(if (contains? session :config)
(:config session)
(conf/load-config "toaster" conf/default-settings))
(f/fail "Session not found. ")))
(defn check-account [request]
;; check if login is present in session
(f/attempt-all
[login (get-in request [:session :auth :email])
user (auth/get-account @ring/auth login)]
user
(f/when-failed [e]
(->> e f/message
(str "Unauthorized access. ")
f/fail))))
(defn check-database []
(if-let [db @ring/db]
db
(f/fail "No connection to database. ")))
(defn check [request fun]
(f/attempt-all
[db (check-database)
config (check-config request)
account (check-account request)]
(fun request config account)
(f/when-failed [e]
(web/render
[:div
(web/render-error (f/message e))
web/login-form]))))

View File

@ -0,0 +1,343 @@
;; Copyright (C) 2015-2018 Dyne.org foundation
;; Sourcecode designed, written and maintained by
;; Denis Roio <jaromil@dyne.org>
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU Affero General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU Affero General Public License for more details.
;; You should have received a copy of the GNU Affero General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
(ns toaster.webpage
(:require [clojure.java.io :as io]
[clojure.data.json :as json]
[clojure.data.csv :as csv]
[yaml.core :as yaml]
[toaster.config :as conf]
[taoensso.timbre :as log]
[failjure.core :as f]
[toaster.ring :as ring]
[hiccup.page :as page]
[hiccup.form :as hf]))
(declare render)
(declare render-head)
(declare navbar-guest)
(declare navbar-account)
(declare render-footer)
(declare render-yaml)
(declare render-edn)
(declare render-error)
(declare render-error-page)
(declare render-static)
(defn q [req]
"wrapper to retrieve parameters"
;; TODO: sanitise and check for irregular chars
(get-in req (conj [:params] req)))
(defn button
([url text] (button url text [:p]))
([url text field] (button url text field "btn-secondary btn-lg"))
([url text field type]
(hf/form-to [:post url]
field ;; can be an hidden key/value field (project,
;; person, etc using hf/hidden-field)
(hf/submit-button {:class (str "btn " type)} text))))
(defn button-cancel-submit [argmap]
[:div
{:class
(str "row col-md-6 btn-group btn-group-lg "
(:btn-group-class argmap))
:role "group"}
(button
(:cancel-url argmap) "Cancel"
(:cancel-params argmap)
"btn-primary btn-lg btn-danger col-md-3")
(button
(:submit-url argmap) "Submit"
(:submit-params argmap)
"btn-primary btn-lg btn-success col-md-3")])
(defn reload-session [request]
;; TODO: validation of all data loaded via prismatic schema
(conf/load-config "toaster" conf/default-settings)
)
(defn render
([body]
{:headers {"Content-Type"
"text/html; charset=utf-8"}
:body (page/html5
(render-head)
[:body ;; {:class "static"}
navbar-guest
[:div {:class "container-fluid"} body]
(render-footer)])})
([account body]
{:headers {"Content-Type"
"text/html; charset=utf-8"}
:body (page/html5
(render-head)
[:body (if (empty? account)
navbar-guest
navbar-account)
[:div {:class "container-fluid"} body]
(render-footer)])}))
(defn render-error
"render an error message without ending the page"
[err]
[:div {:class "alert alert-danger" :role "alert"}
[:span {:class "far fa-meh"
:aria-hidden "true" :style "padding: .5em"}]
[:span {:class "sr-only"} "Error:" ]
err])
(defn render-error-page
([] (render-error-page {} "Unknown"))
([err] (render-error-page {} err))
([session error]
(render
[:div {:class "container-fluid"}
(render-error error)
(if-not (empty? session)
[:div {:class "config"}
[:h2 "Environment dump:"]
(render-yaml session)])])))
(defn render-head
([] (render-head
"toaster" ;; default title
"toaster"
"https://toaster.dyne.org")) ;; default desc
([title desc url]
[:head [:meta {:charset "utf-8"}]
[:meta {:http-equiv "X-UA-Compatible" :content "IE=edge,chrome=1"}]
[:meta
{:name "viewport"
:content "width=device-width, initial-scale=1, maximum-scale=1"}]
[:title title]
;; javascript scripts
(page/include-js "/static/js/jquery-3.2.1.min.js")
(page/include-js "/static/js/bootstrap.min.js")
;; cascade style sheets
(page/include-css "/static/css/bootstrap.min.css")
(page/include-css "/static/css/json-html.css")
(page/include-css "/static/css/highlight-tomorrow.css")
(page/include-css "/static/css/formatters-styles/html.css")
(page/include-css "/static/css/formatters-styles/annotated.css")
(page/include-css "/static/css/fa-regular.min.css")
(page/include-css "/static/css/fontawesome.min.css")
(page/include-css "/static/css/toaster.css")]))
(def navbar-guest
[:nav
{:class "navbar navbar-default navbar-fixed-top navbar-expand-md navbar-expand-lg"}
[:div {:class "navbar-header"}
[:button {:class "navbar-toggle" :type "button"
:data-toggle "collapse"
:data-target "#navbarResponsive"
:aria-controls "navbarResponsive"
:aria-expanded "false"
:aria-label "Toggle navigation"}
[:span {:class "sr-only"} "Toggle navigation"]
[:span {:class "icon-bar"}]
[:span {:class "icon-bar"}]
[:span {:class "icon-bar"}]]
[:a {:class "navbar-brand far fa-handshake" :href "/"} "toaster"]]
[:div {:class "collapse navbar-collapse" :id "navbarResponsive"}
[:ul {:class "nav navbar-nav hidden-sm hidden-md ml-auto"}
;; --
[:li {:class "divider" :role "separator"}]
[:li {:class "nav-item"}
[:a {:class "nav-link far fa-address-card"
:href "/login"} " Login"]]
]]])
(def navbar-account
[:nav {:class "navbar navbar-default navbar-fixed-top navbar-expand-lg"}
[:div {:class "navbar-header"}
[:button {:class "navbar-toggle" :type "button"
:data-toggle "collapse" :data-target "#navbarResponsive"
:aria-controls "navbarResponsive" :aria-expanded "false"
:aria-label "Toggle navigation"}
[:span {:class "sr-only"} "Toggle navigation"]
[:span {:class "icon-bar"}]
[:span {:class "icon-bar"}]
[:span {:class "icon-bar"}]]
[:a {:class "navbar-brand far fa-handshake" :href "/"} "toaster"]]
[:div {:class "collapse navbar-collapse" :id "navbarResponsive"}
[:ul {:class "nav navbar-nav hidden-sm hidden-md ml-auto"}
;; --
[:li {:class "divider" :role "separator"}]
;; LIST OF RELEVANT LINKS AFTER LOGIN
;; [:li {:class "nav-item"}
;; [:a {:class "nav-link far fa-address-card"
;; :href "/persons/list"} " Persons"]]
;; [:li {:class "nav-item"}
;; [:a {:class "nav-link far fa-paper-plane"
;; :href "/projects/list"} " Projects"]]
;; [:li {:class "nav-item"}
;; [:a {:class "nav-link far fa-plus-square"
;; :href "/timesheets"} " Upload"]]
;; [:li {:class "nav-item"}
;; [:a {:class "nav-link far fa-save"
;; :href "/reload"} " Reload"]]
;; --
[:li {:role "separator" :class "divider"} ]
[:li {:class "nav-item"}
[:a {:class "nav-link far fa-file-code"
:href "/config"} " Configuration"]]
]]])
(defn render-footer []
[:footer {:class "row" :style "margin: 20px"}
[:hr]
[:div {:class "footer col-lg-3"}
[:img {:src "/static/img/AGPLv3.png" :style "margin-top: 2.5em"
:alt "Affero GPLv3 License"
:title "Affero GPLv3 License"} ]]
[:div {:class "footer col-lg-3"}
[:a {:href "https://www.dyne.org"}
[:img {:src "/static/img/swbydyne.png"
:alt "Software by Dyne.org"
:title "Software by Dyne.org"}]]]
])
(defn render-static [body]
(page/html5 (render-head)
[:body {:class "fxc static"}
navbar-guest
[:div {:class "container"} body]
(render-footer)
]))
;; highlight functions do no conversion, take the format they highlight
;; render functions take edn and convert to the highlight format
;; download functions all take an edn and convert it in target format
;; edit functions all take an edn and present an editor in the target format
(defn render-yaml
"renders an edn into an highlighted yaml"
[data]
[:span
[:pre [:code {:class "yaml"}
(yaml/generate-string data)]]
[:script "hljs.initHighlightingOnLoad();"]])
(defn highlight-yaml
"renders a yaml text in highlighted html"
[data]
[:span
[:pre [:code {:class "yaml"}
data]]
[:script "hljs.initHighlightingOnLoad();"]])
(defn highlight-json
"renders a json text in highlighted html"
[data]
[:span
[:pre [:code {:class "json"}
data]]
[:script "hljs.initHighlightingOnLoad();"]])
(defn download-csv
"takes an edn, returns a csv plain/text for download"
[data]
{:headers {"Content-Type"
"text/plain; charset=utf-8"}
:body (with-out-str (csv/write-csv *out* data))})
(defn edit-edn
"renders an editor for the edn in yaml format"
[data]
[:div;; {:class "form-group"}
[:textarea {:class "form-control"
:rows "20" :data-editor "yaml"
:id "config" :name "editor"}
(yaml/generate-string data)]
[:script {:src "/static/js/ace.js"
:type "text/javascript" :charset "utf-8"}]
[:script {:type "text/javascript"}
(slurp (io/resource "public/static/js/ace-embed.js"))]
;; code to embed the ace editor on all elements in page
;; that contain the attribute "data-editor" set to the
;; mode language of choice
[:input {:class "btn btn-success btn-lg pull-top"
:type "submit" :value "submit"}]])
;; (defonce readme
;; (slurp (io/resource "public/static/README.html")))
(defonce login-form
[:div
[:h1 "Login for your account"
[:form {:action "/login"
:method "post"}
[:input {:type "text" :name "username"
:placeholder "Username"
:class "form-control"
:style "margin-top: 1em"}]
[:input {:type "password" :name "password"
:placeholder "Password"
:class "form-control"
:style "margin-top: 1em"}]
[:input {:type "submit" :value "Login"
:class "btn btn-primary btn-lg btn-block"
:style "margin-top: 1em"}]]]])
(defonce signup-form
[:div
[:h1 "Sign Up for a toaster account"
[:form {:action "/signup"
:method "post"}
[:input {:type "text" :name "name"
:placeholder "Name"
:class "form-control"
:style "margin-top: 1em"}]
[:input {:type "text" :name "email"
:placeholder "Email"
:class "form-control"
:style "margin-top: 1em"}]
[:input {:type "password" :name "password"
:placeholder "Password"
:class "form-control"
:style "margin-top: 1em"}]
[:input {:type "password" :name "repeat-password"
:placeholder "Repeat password"
:class "form-control"
:style "margin-top: 1em"}]
[:input {:type "submit" :value "Sign In"
:class "btn btn-primary btn-lg btn-block"
:style "margin-top: 1em"}]]]])

View File

@ -0,0 +1,16 @@
webserver:
anti-forgery: false
ssl-redirect: false
admin:
email: jaromil@dyne.org
just-auth:
email-server: "mail.dyne.org"
email-user: "xxxxxxxxxxxxxxxx"
email-pass: "xxxxxxxxxxxxxxxx"
email-address: "xxxxxxxxxxxxxxxx"
email-admin: "xxxxxxxxxxxxxxxx"
mongo-url: mongodb://localhost:27017/toaster
mongo-user: toaster
mongo-pass: toaster