finish implementing authentication
This commit is contained in:
parent
ef9544ff7d
commit
438f9fc7ae
|
@ -94,7 +94,7 @@ func checkAuthorised(dbConn *sql.DB, username string) {
|
||||||
}
|
}
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
|
|
||||||
authorised, err := users.Authorised(dbConn, username, string(password))
|
authorised, err := users.UserAuthorised(dbConn, username, string(password))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Error checking authorisation:", err)
|
fmt.Println("Error checking authorisation:", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|
|
@ -109,6 +109,7 @@ func main() {
|
||||||
mux.HandleFunc("/static", ws.StaticHandler)
|
mux.HandleFunc("/static", ws.StaticHandler)
|
||||||
mux.HandleFunc("/new", wsHandler.NewHandler)
|
mux.HandleFunc("/new", wsHandler.NewHandler)
|
||||||
mux.HandleFunc("/login", wsHandler.LoginHandler)
|
mux.HandleFunc("/login", wsHandler.LoginHandler)
|
||||||
|
mux.HandleFunc("/logout", wsHandler.LogoutHandler)
|
||||||
|
|
||||||
httpServer := &http.Server{
|
httpServer := &http.Server{
|
||||||
Addr: config.Server.Listen,
|
Addr: config.Server.Listen,
|
||||||
|
|
|
@ -64,6 +64,7 @@ func fetchReleases(p Project) (Project, error) {
|
||||||
case "github", "gitea", "forgejo":
|
case "github", "gitea", "forgejo":
|
||||||
rssReleases, err := rss.GetReleases(p.URL)
|
rssReleases, err := rss.GetReleases(p.URL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
fmt.Println("Error getting RSS releases:", err)
|
||||||
return p, err
|
return p, err
|
||||||
}
|
}
|
||||||
for _, release := range rssReleases {
|
for _, release := range rssReleases {
|
||||||
|
|
|
@ -6,6 +6,7 @@ package rss
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/microcosm-cc/bluemonday"
|
"github.com/microcosm-cc/bluemonday"
|
||||||
|
@ -27,7 +28,8 @@ var (
|
||||||
|
|
||||||
func GetReleases(feedURL string) ([]Release, error) {
|
func GetReleases(feedURL string) ([]Release, error) {
|
||||||
fp := gofeed.NewParser()
|
fp := gofeed.NewParser()
|
||||||
feed, err := fp.ParseURL(feedURL + "/releases.atom")
|
|
||||||
|
feed, err := fp.ParseURL(strings.TrimSuffix(feedURL, "/") + "/releases.atom")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -44,8 +46,5 @@ func GetReleases(feedURL string) ([]Release, error) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Doesn't seem to work?
|
|
||||||
// sort.Slice(p.Releases, func(i, j int) bool { return p.Releases[i].Date.After(p.Releases[j].Date) })
|
|
||||||
|
|
||||||
return releases, nil
|
return releases, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,9 +55,9 @@ func Register(dbConn *sql.DB, username, password string) error {
|
||||||
// Delete removes a user from the database.
|
// Delete removes a user from the database.
|
||||||
func Delete(dbConn *sql.DB, username string) error { return db.DeleteUser(dbConn, username) }
|
func Delete(dbConn *sql.DB, username string) error { return db.DeleteUser(dbConn, username) }
|
||||||
|
|
||||||
// Authorised accepts a username string, a token string, and returns true if the
|
// UserAuthorised accepts a username string, a token string, and returns true if the
|
||||||
// user is authorised, false if not, and an error if one is encountered.
|
// user is authorised, false if not, and an error if one is encountered.
|
||||||
func Authorised(dbConn *sql.DB, username, token string) (bool, error) {
|
func UserAuthorised(dbConn *sql.DB, username, token string) (bool, error) {
|
||||||
dbHash, dbSalt, err := db.GetUser(dbConn, username)
|
dbHash, dbSalt, err := db.GetUser(dbConn, username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
|
@ -71,21 +71,38 @@ func Authorised(dbConn *sql.DB, username, token string) (bool, error) {
|
||||||
return dbHash == providedHash, nil
|
return dbHash == providedHash, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSession accepts a session cookie string and returns the username
|
// SessionAuthorised accepts a session string and returns true if the session is
|
||||||
func GetSession(dbConn *sql.DB, session string) (string, time.Time, error) {
|
// valid and false if not.
|
||||||
return db.GetSession(dbConn, session)
|
func SessionAuthorised(dbConn *sql.DB, session string) (bool, error) {
|
||||||
|
dbResult, expiry, err := db.GetSession(dbConn, session)
|
||||||
|
if dbResult == "" || expiry.Before(time.Now()) || err != nil {
|
||||||
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// InvalidateSession invalidates a session by setting the expiration date to the
|
return true, nil
|
||||||
// current time.
|
}
|
||||||
|
|
||||||
|
// InvalidateSession invalidates a session by setting the expiration date to now.
|
||||||
func InvalidateSession(dbConn *sql.DB, session string) error {
|
func InvalidateSession(dbConn *sql.DB, session string) error {
|
||||||
return db.InvalidateSession(dbConn, session, time.Now())
|
return db.InvalidateSession(dbConn, session, time.Now())
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateSession accepts a username and a token and creates a session in the
|
// CreateSession accepts a username, generates a token, stores it in the
|
||||||
// database.
|
// database, and returns it
|
||||||
func CreateSession(dbConn *sql.DB, username, token string, expiry time.Time) error {
|
func CreateSession(dbConn *sql.DB, username string) (string, time.Time, error) {
|
||||||
return db.CreateSession(dbConn, username, token, expiry)
|
token, err := generateSalt()
|
||||||
|
if err != nil {
|
||||||
|
return "", time.Time{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
expiry := time.Now().Add(7 * 24 * time.Hour)
|
||||||
|
|
||||||
|
err = db.CreateSession(dbConn, username, token, expiry)
|
||||||
|
if err != nil {
|
||||||
|
return "", time.Time{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return token, expiry, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUsers returns a list of all users in the database as a slice of strings.
|
// GetUsers returns a list of all users in the database as a slice of strings.
|
||||||
|
|
|
@ -22,6 +22,9 @@ html {
|
||||||
}
|
}
|
||||||
.project > h2 > span {
|
.project > h2 > span {
|
||||||
float: right;
|
float: right;
|
||||||
|
}
|
||||||
|
.project > details > pre {
|
||||||
|
overflow: scroll;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
|
@ -30,8 +30,8 @@ html {
|
||||||
<label for="github">Github</label><br>
|
<label for="github">Github</label><br>
|
||||||
<input type="radio" id="gitea" name="forge" value="gitea">
|
<input type="radio" id="gitea" name="forge" value="gitea">
|
||||||
<label for="gitea">Gitea</label><br>
|
<label for="gitea">Gitea</label><br>
|
||||||
<input type="radio" id="Forgejo" name="forge" value="Forgejo">
|
<input type="radio" id="forgejo" name="forge" value="forgejo">
|
||||||
<label for="Forgejo">Forgejo</label><br>
|
<label for="forgejo">Forgejo</label><br>
|
||||||
<p>Raw git</p>
|
<p>Raw git</p>
|
||||||
<input type="radio" id="gitlab" name="forge" value="gitlab">
|
<input type="radio" id="gitlab" name="forge" value="gitlab">
|
||||||
<label for="gitlab">GitLab</label><br>
|
<label for="gitlab">GitLab</label><br>
|
||||||
|
|
112
ws/ws.go
112
ws/ws.go
|
@ -8,12 +8,14 @@ import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"embed"
|
"embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"git.sr.ht/~amolith/willow/users"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
"time"
|
||||||
|
|
||||||
"git.sr.ht/~amolith/willow/project"
|
"git.sr.ht/~amolith/willow/project"
|
||||||
"github.com/microcosm-cc/bluemonday"
|
"github.com/microcosm-cc/bluemonday"
|
||||||
|
@ -153,14 +155,120 @@ func (h Handler) NewHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h Handler) LoginHandler(w http.ResponseWriter, r *http.Request) {
|
func (h Handler) LoginHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
// TODO: do this
|
if r.Method == http.MethodGet {
|
||||||
|
if h.isAuthorised(r) {
|
||||||
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
login, err := fs.ReadFile("static/login.html")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error reading login.html:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := io.WriteString(w, string(login)); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Method == http.MethodPost {
|
||||||
|
err := r.ParseForm()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
username := bmStrict.Sanitize(r.FormValue("username"))
|
||||||
|
password := bmStrict.Sanitize(r.FormValue("password"))
|
||||||
|
|
||||||
|
if username == "" || password == "" {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
_, err := w.Write([]byte("No data provided"))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
authorised, err := users.UserAuthorised(h.DbConn, username, password)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
_, err := w.Write([]byte(fmt.Sprintf("Error logging in: %s", err)))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !authorised {
|
||||||
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
_, err := w.Write([]byte("Incorrect username or password"))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
session, expiry, err := users.CreateSession(h.DbConn, username)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
_, err := w.Write([]byte(fmt.Sprintf("Error creating session: %s", err)))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
maxAge := int(expiry.Sub(time.Now()).Seconds())
|
||||||
|
|
||||||
|
cookie := http.Cookie{
|
||||||
|
Name: "id",
|
||||||
|
Value: session,
|
||||||
|
MaxAge: maxAge,
|
||||||
|
HttpOnly: true,
|
||||||
|
SameSite: http.SameSiteStrictMode,
|
||||||
|
Secure: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
http.SetCookie(w, &cookie)
|
||||||
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h Handler) LogoutHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
cookie, err := r.Cookie("id")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = users.InvalidateSession(h.DbConn, cookie.Value)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
_, err = w.Write([]byte(fmt.Sprintf("Error logging out: %s", err)))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cookie.MaxAge = -1
|
||||||
|
http.SetCookie(w, cookie)
|
||||||
|
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||||
|
}
|
||||||
|
|
||||||
|
// isAuthorised makes a database request to the sessions table to see if the
|
||||||
|
// user has a valid session cookie.
|
||||||
func (h Handler) isAuthorised(r *http.Request) bool {
|
func (h Handler) isAuthorised(r *http.Request) bool {
|
||||||
// TODO: do this
|
cookie, err := r.Cookie("id")
|
||||||
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
authorised, err := users.SessionAuthorised(h.DbConn, cookie.Value)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error checking session:", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return authorised
|
||||||
|
}
|
||||||
|
|
||||||
func StaticHandler(writer http.ResponseWriter, request *http.Request) {
|
func StaticHandler(writer http.ResponseWriter, request *http.Request) {
|
||||||
resource := strings.TrimPrefix(request.URL.Path, "/")
|
resource := strings.TrimPrefix(request.URL.Path, "/")
|
||||||
// if path ends in .css, set content type to text/css
|
// if path ends in .css, set content type to text/css
|
||||||
|
|
Loading…
Reference in New Issue