Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

User authentication API #7

Merged
merged 5 commits into from Mar 11, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion project.clj
Expand Up @@ -2,7 +2,8 @@
:description "FIXME: write description"
:url "https://github.com/kalouantonis/channel"

:dependencies [[compojure "1.5.1"]
:dependencies [[buddy "1.3.0"]
[compojure "1.5.1"]
[conman "0.6.2"]
[cprop "0.1.9"]
[luminus-immutant "0.2.2"]
Expand Down
6 changes: 5 additions & 1 deletion src/clj/channel/middleware.clj
@@ -1,11 +1,14 @@
(ns channel.middleware
(:require [channel.env :refer [defaults]]
(:require [buddy.auth.backends :as backends]
[buddy.auth.middleware :refer [wrap-authentication]]
[channel.env :refer [defaults]]
[clojure.tools.logging :as log]
[channel.layout :refer [*app-context* error-page]]
[ring.middleware.anti-forgery :refer [wrap-anti-forgery]]
[ring.middleware.webjars :refer [wrap-webjars]]
[ring.middleware.format :refer [wrap-restful-format]]
[channel.config :refer [env]]
[mount.core :as mount]
[ring.middleware.defaults :refer [site-defaults wrap-defaults]])
(:import [javax.servlet ServletContext]))

Expand Down Expand Up @@ -53,6 +56,7 @@

(defn wrap-base [handler]
(-> ((:middleware defaults) handler)
(wrap-authentication (backends/jws {:secret (env :jwt-secret)}))
wrap-webjars
(wrap-defaults
(-> site-defaults
Expand Down
37 changes: 23 additions & 14 deletions src/clj/channel/routes/services.clj
@@ -1,50 +1,59 @@
(ns channel.routes.services
(:require [channel.routes.services.songs :as songs]
(:require [channel.routes.services.auth :as auth]
[channel.routes.services.songs :as songs]
[compojure.api.sweet :refer :all]
[compojure.api.upload :as upload]
[ring.util.http-response :as ring-response]
[schema.core :as s]))

(defapi service-routes
{:swagger {:ui "/swagger-ui"
:spec "/swagger.json"
:data {:info {:version "1.0.0"
:title "Sound file API"
:description "API for uploading sound files."}}}}
:title "Channel API"
:description "API for the Channel web app"}}}}

(context "/api/auth" []
:tags ["auth"]

(POST "/login" req
:summary "Authenticate user"
:body-params [username :- s/Str, password :- s/Str]
:return s/Str
(auth/login username password req)))

(context "/api/songs" []
:tags ["songs"]

(GET "/" []
:return [songs/Song]
:summary "Retrieve all songs."
:return [songs/Song]
(songs/all-songs))

;; possible solution is to get the API to request ID3 data first,
;; then submit with the full required track data.
(POST "/" []
:return songs/Song
:multipart-params [file :- upload/TempFileUpload]
:middleware [upload/wrap-multipart-params]
:summary "Create a new song using an MP3 file."
:description "All song data is extracted from the ID3 metadata of the MP3"
:multipart-params [file :- upload/TempFileUpload]
:return songs/Song
:middleware [upload/wrap-multipart-params]
(songs/create-song! file))

(GET "/:id" []
:summary "Retrieve a specific song."
:return (s/maybe songs/Song)
:path-params [id :- Long]
:summary "Retrieve a specific song."
(songs/get-song id))

(PUT "/:id" []
:return songs/Song
:path-params [id :- Long]
:body [song songs/UpdatedSong]
:summary "Update song details."
:path-params [id :- s/Int]
:body [song songs/UpdatedSong]
:return songs/Song
(songs/update-song! id song))

(DELETE "/:id" []
:return nil
:path-params [id :- Long]
:summary "Delete a specific song."
:path-params [id :- s/Int]
:return nil
(songs/delete-song! id))))
30 changes: 30 additions & 0 deletions src/clj/channel/routes/services/auth.clj
@@ -0,0 +1,30 @@
(ns channel.routes.services.auth
(:require [buddy.hashers :as hashers]
[buddy.sign.jwt :as jwt]
[channel.config :refer [env]]
[channel.db.core :as db]
[clojure.tools.logging :as log]
[ring.util.http-response :as ring-response]
[schema.core :as s]))

(s/defschema User {:id s/Int
:username s/Str
:email (s/maybe s/Str)})

(defn create-auth-token [user]
(jwt/sign (dissoc user :password) (env :jwt-secret)))

(defn login
"Authenticate user using their `username` and `password`. Also
expects the request map."
[username password {:keys [remote-addr server-name]}]
(if-let [user (db/user-by-username {:username username})]
(if (hashers/check password (:password user))
(ring-response/ok (create-auth-token user))
(do
;; Log failed logins to monitor against attacks.
;; TODO: make this toggleable, some users may not
;; TODO: want ANY addresses tracked
(log/info "login failed for" username remote-addr server-name)
(ring-response/unauthorized "Invalid login credentials")))
(ring-response/not-found)))
12 changes: 6 additions & 6 deletions src/clj/channel/routes/services/songs.clj
Expand Up @@ -7,13 +7,13 @@
[ring.util.http-response :as ring-response]
[channel.db.core :as db]))

(s/defschema Song {:id Long
:title String
:artist (s/maybe String)
:album (s/maybe String)
:genre (s/maybe String)
(s/defschema Song {:id s/Int
:title s/Str
:artist (s/maybe s/Str)
:album (s/maybe s/Str)
:genre (s/maybe s/Str)
:track s/Int
:file String})
:file s/Str})
;; Data required to update a song
(s/defschema UpdatedSong (dissoc Song :id :file))

Expand Down