parent
d5717f0e7d
commit
d17d17f641
|
@ -0,0 +1,54 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.jolheiser.com/invitea/database"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Invite struct {
|
||||||
|
ID int64
|
||||||
|
Code string
|
||||||
|
Uses int64
|
||||||
|
Total int64
|
||||||
|
Expiration time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Invite) TotalString() string {
|
||||||
|
if i.Total == 0 {
|
||||||
|
return "∞"
|
||||||
|
}
|
||||||
|
return strconv.FormatInt(i.Total, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Invite) ExpirationString() string {
|
||||||
|
if i.Expiration.Unix() == 0 {
|
||||||
|
return "never"
|
||||||
|
}
|
||||||
|
return i.Expiration.Format("01/02/2006")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Invite) Valid() bool {
|
||||||
|
limited := i.Total != 0 && i.Uses >= i.Total
|
||||||
|
expired := i.Expiration.Unix() != 0 && time.Now().After(i.Expiration)
|
||||||
|
return !(limited || expired)
|
||||||
|
}
|
||||||
|
|
||||||
|
func InviteFromDB(i database.Invite) Invite {
|
||||||
|
return Invite{
|
||||||
|
ID: i.ID,
|
||||||
|
Code: i.Code,
|
||||||
|
Uses: i.Uses,
|
||||||
|
Total: i.Total.Int64,
|
||||||
|
Expiration: time.Unix(i.Expiration.Int64, 0).UTC(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func InvitesFromDB(i ...database.Invite) []Invite {
|
||||||
|
invites := make([]Invite, 0, len(i))
|
||||||
|
for _, ii := range i {
|
||||||
|
invites = append(invites, InviteFromDB(ii))
|
||||||
|
}
|
||||||
|
return invites
|
||||||
|
}
|
|
@ -7,8 +7,7 @@ package database
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"database/sql"
|
||||||
"go.jolheiser.com/invitea/database/sqlc"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const countInvites = `-- name: CountInvites :one
|
const countInvites = `-- name: CountInvites :one
|
||||||
|
@ -34,8 +33,8 @@ RETURNING id, code, uses, total, expiration
|
||||||
type CreateInviteParams struct {
|
type CreateInviteParams struct {
|
||||||
Code string
|
Code string
|
||||||
Uses int64
|
Uses int64
|
||||||
Total int64
|
Total sql.NullInt64
|
||||||
Expiration sqlc.Timestamp
|
Expiration sql.NullInt64
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) CreateInvite(ctx context.Context, arg CreateInviteParams) (Invite, error) {
|
func (q *Queries) CreateInvite(ctx context.Context, arg CreateInviteParams) (Invite, error) {
|
||||||
|
@ -68,11 +67,11 @@ func (q *Queries) DeleteInvite(ctx context.Context, id int64) error {
|
||||||
|
|
||||||
const getInvite = `-- name: GetInvite :one
|
const getInvite = `-- name: GetInvite :one
|
||||||
SELECT id, code, uses, total, expiration FROM invites
|
SELECT id, code, uses, total, expiration FROM invites
|
||||||
WHERE id = ? LIMIT 1
|
WHERE code = ? LIMIT 1
|
||||||
`
|
`
|
||||||
|
|
||||||
func (q *Queries) GetInvite(ctx context.Context, id int64) (Invite, error) {
|
func (q *Queries) GetInvite(ctx context.Context, code string) (Invite, error) {
|
||||||
row := q.db.QueryRowContext(ctx, getInvite, id)
|
row := q.db.QueryRowContext(ctx, getInvite, code)
|
||||||
var i Invite
|
var i Invite
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
&i.ID,
|
&i.ID,
|
||||||
|
@ -86,7 +85,7 @@ func (q *Queries) GetInvite(ctx context.Context, id int64) (Invite, error) {
|
||||||
|
|
||||||
const listInvites = `-- name: ListInvites :many
|
const listInvites = `-- name: ListInvites :many
|
||||||
SELECT id, code, uses, total, expiration FROM invites
|
SELECT id, code, uses, total, expiration FROM invites
|
||||||
ORDER BY id
|
ORDER BY id DESC
|
||||||
`
|
`
|
||||||
|
|
||||||
func (q *Queries) ListInvites(ctx context.Context) ([]Invite, error) {
|
func (q *Queries) ListInvites(ctx context.Context) ([]Invite, error) {
|
||||||
|
@ -117,3 +116,19 @@ func (q *Queries) ListInvites(ctx context.Context) ([]Invite, error) {
|
||||||
}
|
}
|
||||||
return items, nil
|
return items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const updateInvite = `-- name: UpdateInvite :exec
|
||||||
|
UPDATE invites
|
||||||
|
SET uses = ?
|
||||||
|
WHERE id = ?
|
||||||
|
`
|
||||||
|
|
||||||
|
type UpdateInviteParams struct {
|
||||||
|
Uses int64
|
||||||
|
ID int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) UpdateInvite(ctx context.Context, arg UpdateInviteParams) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, updateInvite, arg.Uses, arg.ID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
|
@ -5,13 +5,13 @@
|
||||||
package database
|
package database
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"go.jolheiser.com/invitea/database/sqlc"
|
"database/sql"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Invite struct {
|
type Invite struct {
|
||||||
ID int64
|
ID int64
|
||||||
Code string
|
Code string
|
||||||
Uses int64
|
Uses int64
|
||||||
Total int64
|
Total sql.NullInt64
|
||||||
Expiration sqlc.Timestamp
|
Expiration sql.NullInt64
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,6 @@ CREATE TABLE invites (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
code TEXT NOT NULL,
|
code TEXT NOT NULL,
|
||||||
uses INTEGER NOT NULL,
|
uses INTEGER NOT NULL,
|
||||||
total INTEGER NOT NULL,
|
total INTEGER,
|
||||||
expiration DATE NOT NULL
|
expiration INTEGER
|
||||||
);
|
);
|
|
@ -1,10 +1,10 @@
|
||||||
-- name: GetInvite :one
|
-- name: GetInvite :one
|
||||||
SELECT * FROM invites
|
SELECT * FROM invites
|
||||||
WHERE id = ? LIMIT 1;
|
WHERE code = ? LIMIT 1;
|
||||||
|
|
||||||
-- name: ListInvites :many
|
-- name: ListInvites :many
|
||||||
SELECT * FROM invites
|
SELECT * FROM invites
|
||||||
ORDER BY id;
|
ORDER BY id DESC;
|
||||||
|
|
||||||
-- name: CreateInvite :one
|
-- name: CreateInvite :one
|
||||||
INSERT INTO invites (
|
INSERT INTO invites (
|
||||||
|
@ -14,6 +14,11 @@ INSERT INTO invites (
|
||||||
)
|
)
|
||||||
RETURNING *;
|
RETURNING *;
|
||||||
|
|
||||||
|
-- name: UpdateInvite :exec
|
||||||
|
UPDATE invites
|
||||||
|
SET uses = ?
|
||||||
|
WHERE id = ?;
|
||||||
|
|
||||||
-- name: DeleteInvite :exec
|
-- name: DeleteInvite :exec
|
||||||
DELETE FROM invites
|
DELETE FROM invites
|
||||||
WHERE id = ?;
|
WHERE id = ?;
|
||||||
|
|
|
@ -7,6 +7,3 @@ sql:
|
||||||
go:
|
go:
|
||||||
package: "database"
|
package: "database"
|
||||||
out: "../"
|
out: "../"
|
||||||
overrides:
|
|
||||||
- db_type: "DATE"
|
|
||||||
go_type: "go.jolheiser.com/invitea/database/sqlc.Timestamp"
|
|
12
main.go
12
main.go
|
@ -4,15 +4,17 @@ import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/golang-migrate/migrate/v4"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"go.jolheiser.com/invitea/database"
|
"go.jolheiser.com/invitea/database"
|
||||||
"go.jolheiser.com/invitea/database/sqlc/migrations"
|
"go.jolheiser.com/invitea/database/sqlc/migrations"
|
||||||
"go.jolheiser.com/invitea/router"
|
"go.jolheiser.com/invitea/router"
|
||||||
|
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
|
"github.com/golang-migrate/migrate/v4"
|
||||||
"github.com/peterbourgon/ff/v3"
|
"github.com/peterbourgon/ff/v3"
|
||||||
"github.com/peterbourgon/ff/v3/ffyaml"
|
"github.com/peterbourgon/ff/v3/ffyaml"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
|
@ -63,11 +65,17 @@ func main() {
|
||||||
log.Fatal().Err(err).Msg("could not migrate database")
|
log.Fatal().Err(err).Msg("could not migrate database")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
client, err := gitea.NewClient(*giteaURLFlag, gitea.SetToken(*giteaTokenFlag))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("could not create gitea client")
|
||||||
|
}
|
||||||
|
|
||||||
r := router.New(router.Config{
|
r := router.New(router.Config{
|
||||||
Domain: *domainFlag,
|
Domain: *domainFlag,
|
||||||
SessionSecret: *sessionSecretFlag,
|
SessionSecret: *sessionSecretFlag,
|
||||||
Database: database.New(db),
|
Database: database.New(db),
|
||||||
GiteaURL: *giteaURLFlag,
|
GiteaClient: client,
|
||||||
|
GiteaURL: strings.TrimSuffix(*giteaURLFlag, "/"),
|
||||||
GiteaClientKey: *giteaClientKeyFlag,
|
GiteaClientKey: *giteaClientKeyFlag,
|
||||||
GiteaClientSecret: *giteaClientSecretFlag,
|
GiteaClientSecret: *giteaClientSecretFlag,
|
||||||
GiteaToken: *giteaTokenFlag,
|
GiteaToken: *giteaTokenFlag,
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"go.jolheiser.com/invitea/database"
|
"go.jolheiser.com/invitea/database"
|
||||||
"go.jolheiser.com/invitea/static"
|
"go.jolheiser.com/invitea/static"
|
||||||
|
|
||||||
|
sdk "code.gitea.io/sdk/gitea"
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/go-chi/chi/v5/middleware"
|
"github.com/go-chi/chi/v5/middleware"
|
||||||
"github.com/gorilla/sessions"
|
"github.com/gorilla/sessions"
|
||||||
|
@ -21,6 +22,7 @@ type Config struct {
|
||||||
Domain string
|
Domain string
|
||||||
SessionSecret string
|
SessionSecret string
|
||||||
Database *database.Queries
|
Database *database.Queries
|
||||||
|
GiteaClient *sdk.Client
|
||||||
|
|
||||||
GiteaURL string
|
GiteaURL string
|
||||||
GiteaClientKey string
|
GiteaClientKey string
|
||||||
|
@ -51,6 +53,8 @@ func New(cfg Config) *chi.Mux {
|
||||||
|
|
||||||
routes := Routes{
|
routes := Routes{
|
||||||
DB: cfg.Database,
|
DB: cfg.Database,
|
||||||
|
Gitea: cfg.GiteaClient,
|
||||||
|
GiteaURL: cfg.GiteaURL,
|
||||||
}
|
}
|
||||||
|
|
||||||
mux := chi.NewMux()
|
mux := chi.NewMux()
|
||||||
|
@ -67,6 +71,12 @@ func New(cfg Config) *chi.Mux {
|
||||||
mux.Route("/", func(r chi.Router) {
|
mux.Route("/", func(r chi.Router) {
|
||||||
r.Use(store.Middleware)
|
r.Use(store.Middleware)
|
||||||
r.Get("/", routes.Index)
|
r.Get("/", routes.Index)
|
||||||
|
r.With(store.RequireAuth).Post("/", routes.IndexPost)
|
||||||
|
})
|
||||||
|
|
||||||
|
mux.Route("/invite/{code}", func(r chi.Router) {
|
||||||
|
r.Get("/", routes.Invite)
|
||||||
|
r.Post("/", routes.InvitePost)
|
||||||
})
|
})
|
||||||
|
|
||||||
mux.Route("/auth", func(r chi.Router) {
|
mux.Route("/auth", func(r chi.Router) {
|
||||||
|
@ -83,6 +93,17 @@ func New(cfg Config) *chi.Mux {
|
||||||
}
|
}
|
||||||
http.Redirect(w, r, cfg.Domain, http.StatusFound)
|
http.Redirect(w, r, cfg.Domain, http.StatusFound)
|
||||||
})
|
})
|
||||||
|
r.Get("/logout", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if err := gothic.Logout(w, r); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := store.Logout(w, r); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
http.Redirect(w, r, cfg.Domain, http.StatusFound)
|
||||||
|
})
|
||||||
r.Get("/callback", func(w http.ResponseWriter, r *http.Request) {
|
r.Get("/callback", func(w http.ResponseWriter, r *http.Request) {
|
||||||
user, err := gothic.CompleteUserAuth(w, r)
|
user, err := gothic.CompleteUserAuth(w, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
150
router/routes.go
150
router/routes.go
|
@ -1,16 +1,26 @@
|
||||||
package router
|
package router
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"database/sql"
|
||||||
|
"encoding/hex"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.jolheiser.com/invitea/api"
|
||||||
"go.jolheiser.com/invitea/database"
|
"go.jolheiser.com/invitea/database"
|
||||||
"go.jolheiser.com/invitea/static"
|
"go.jolheiser.com/invitea/static"
|
||||||
|
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Routes struct {
|
type Routes struct {
|
||||||
DB *database.Queries
|
DB *database.Queries
|
||||||
|
Gitea *gitea.Client
|
||||||
|
GiteaURL string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ro *Routes) Index(w http.ResponseWriter, r *http.Request) {
|
func (ro *Routes) Index(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -19,15 +29,151 @@ func (ro *Routes) Index(w http.ResponseWriter, r *http.Request) {
|
||||||
isAdmin = ia
|
isAdmin = ia
|
||||||
}
|
}
|
||||||
|
|
||||||
invites, err := ro.DB.ListInvites(r.Context())
|
dbInvites, err := ro.DB.ListInvites(r.Context())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Msg("")
|
log.Err(err).Msg("")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := static.Templates.ExecuteTemplate(w, "index.tmpl", map[string]any{
|
if err := static.Templates.ExecuteTemplate(w, "index.tmpl", map[string]any{
|
||||||
"isAdmin": isAdmin,
|
"isAdmin": isAdmin,
|
||||||
"invites": invites,
|
"username": r.Context().Value("username"),
|
||||||
|
"invites": api.InvitesFromDB(dbInvites...),
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
log.Err(err).Msg("")
|
log.Err(err).Msg("")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ro *Routes) IndexPost(w http.ResponseWriter, r *http.Request) {
|
||||||
|
action := r.FormValue("action")
|
||||||
|
if action == "" {
|
||||||
|
http.Redirect(w, r, "/", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch action {
|
||||||
|
case "delete":
|
||||||
|
id, err := strconv.ParseInt(r.FormValue("id"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "invalid ID", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := ro.DB.DeleteInvite(r.Context(), id); err != nil {
|
||||||
|
log.Err(err).Msg("could not delete invite")
|
||||||
|
http.Error(w, "could not delete invite", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "create":
|
||||||
|
u := r.FormValue("uses")
|
||||||
|
var uses int64
|
||||||
|
if u != "" {
|
||||||
|
var err error
|
||||||
|
if uses, err = strconv.ParseInt(u, 10, 64); err != nil {
|
||||||
|
http.Error(w, "invalid uses", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
e := r.FormValue("expiration")
|
||||||
|
var expiration int64
|
||||||
|
if e != "" {
|
||||||
|
ex, err := time.Parse("2006-01-02", e)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "invalid expiration", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
expiration = ex.Unix()
|
||||||
|
}
|
||||||
|
|
||||||
|
code := make([]byte, 5)
|
||||||
|
if _, err := rand.Read(code); err != nil {
|
||||||
|
log.Err(err).Msg("could not generate code")
|
||||||
|
http.Error(w, "could not generate code", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := ro.DB.CreateInvite(r.Context(), database.CreateInviteParams{
|
||||||
|
Code: hex.EncodeToString(code),
|
||||||
|
Total: sql.NullInt64{
|
||||||
|
Int64: uses,
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
Expiration: sql.NullInt64{
|
||||||
|
Int64: expiration,
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
}); err != nil {
|
||||||
|
log.Err(err).Msg("could not create invite")
|
||||||
|
http.Error(w, "could not create invite", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ro *Routes) Invite(w http.ResponseWriter, r *http.Request) {
|
||||||
|
code := chi.URLParam(r, "code")
|
||||||
|
|
||||||
|
dbInvite, err := ro.DB.GetInvite(r.Context(), code)
|
||||||
|
if err != nil {
|
||||||
|
log.Err(err).Str("code", code).Msg("could not get invite")
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
invite := api.InviteFromDB(dbInvite)
|
||||||
|
if !invite.Valid() {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := static.Templates.ExecuteTemplate(w, "invite.tmpl", map[string]any{
|
||||||
|
"invite": invite,
|
||||||
|
}); err != nil {
|
||||||
|
log.Err(err).Msg("")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ro *Routes) InvitePost(w http.ResponseWriter, r *http.Request) {
|
||||||
|
code := chi.URLParam(r, "code")
|
||||||
|
|
||||||
|
dbInvite, err := ro.DB.GetInvite(r.Context(), code)
|
||||||
|
if err != nil {
|
||||||
|
log.Err(err).Msg(code)
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
invite := api.InviteFromDB(dbInvite)
|
||||||
|
if !invite.Valid() {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
username := r.FormValue("username")
|
||||||
|
password := r.FormValue("password")
|
||||||
|
email := r.FormValue("email")
|
||||||
|
|
||||||
|
var b bool
|
||||||
|
if _, _, err := ro.Gitea.AdminCreateUser(gitea.CreateUserOption{
|
||||||
|
Username: username,
|
||||||
|
Email: email,
|
||||||
|
Password: password,
|
||||||
|
MustChangePassword: &b,
|
||||||
|
}); err != nil {
|
||||||
|
log.Err(err).Msg("could not create user")
|
||||||
|
http.Error(w, "could not create user", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ro.DB.UpdateInvite(r.Context(), database.UpdateInviteParams{
|
||||||
|
ID: dbInvite.ID,
|
||||||
|
Uses: dbInvite.Uses + 1,
|
||||||
|
}); err != nil {
|
||||||
|
log.Err(err).Msg("could not update invite")
|
||||||
|
http.Error(w, "could not update invite", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
http.Redirect(w, r, ro.GiteaURL+"/user/login", http.StatusSeeOther)
|
||||||
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ func (s *SessionStore) Middleware(next http.Handler) http.Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
r = r.WithContext(context.WithValue(r.Context(), "isAdmin", sess.Values["isAdmin"]))
|
r = r.WithContext(context.WithValue(r.Context(), "isAdmin", sess.Values["isAdmin"]))
|
||||||
|
r = r.WithContext(context.WithValue(r.Context(), "username", sess.Values["username"]))
|
||||||
next.ServeHTTP(w, r)
|
next.ServeHTTP(w, r)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -62,11 +63,22 @@ func (s *SessionStore) Auth(w http.ResponseWriter, r *http.Request, token string
|
||||||
}
|
}
|
||||||
sess.Values["authenticated"] = true
|
sess.Values["authenticated"] = true
|
||||||
sess.Values["isAdmin"] = profile.IsAdmin
|
sess.Values["isAdmin"] = profile.IsAdmin
|
||||||
|
sess.Values["username"] = profile.UserName
|
||||||
|
return s.Store.Save(r, w, sess)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SessionStore) Logout(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
sess, err := s.Store.Get(r, sessionCookie)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sess.Options.MaxAge = -1
|
||||||
return s.Store.Save(r, w, sess)
|
return s.Store.Save(r, w, sess)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSessionStore(sessionSecret, giteURL string) *SessionStore {
|
func NewSessionStore(sessionSecret, giteURL string) *SessionStore {
|
||||||
store := sessions.NewCookieStore([]byte(sessionSecret))
|
store := sessions.NewCookieStore([]byte(sessionSecret))
|
||||||
|
store.MaxAge(0)
|
||||||
return &SessionStore{
|
return &SessionStore{
|
||||||
Store: store,
|
Store: store,
|
||||||
GiteaURL: giteURL,
|
GiteaURL: giteURL,
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<input type="hidden" name="action" value="create"/>
|
<input type="hidden" name="action" value="create"/>
|
||||||
<p>
|
<p>
|
||||||
<label for="uses">Number of Uses (Leave at 0 for unlimited)</label>
|
<label for="uses">Number of Uses (Leave at 0 for unlimited)</label>
|
||||||
<input id="uses" type="number" name="uses" value="0" min="0"/>
|
<input id="uses" type="number" name="uses" placeholder="0" min="0"/>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<label for="expiration">Expiration (Leave blank for no expiration)</label>
|
<label for="expiration">Expiration (Leave blank for no expiration)</label>
|
||||||
|
@ -24,10 +24,22 @@
|
||||||
<tbody>
|
<tbody>
|
||||||
{{ range .invites }}
|
{{ range .invites }}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ .Code }}</td>
|
<td>
|
||||||
<td>{{ .Uses }}/{{ .Total }}</td>
|
{{ if .Valid -}}
|
||||||
<td>{{ .Expiration }}</td>
|
<a href="/invite/{{ .Code }}">{{ .Code }}</a>
|
||||||
<td>Delete</td>
|
{{- else -}}
|
||||||
|
<code>{{ .Code }}</code>
|
||||||
|
{{- end }}
|
||||||
|
</td>
|
||||||
|
<td>{{ .Uses }} / {{ .TotalString }}</td>
|
||||||
|
<td>{{ .ExpirationString }}</td>
|
||||||
|
<td>
|
||||||
|
<form method="POST">
|
||||||
|
<input type="hidden" name="action" value="delete"/>
|
||||||
|
<input type="hidden" name="id" value="{{.ID}}"/>
|
||||||
|
<button type="submit">Delete</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
|
@ -12,6 +12,9 @@
|
||||||
</header>
|
</header>
|
||||||
<main>
|
<main>
|
||||||
{{ if .isAdmin }}
|
{{ if .isAdmin }}
|
||||||
|
<p>
|
||||||
|
Hello, {{ .username }}. <a href="./auth/logout">Log Out</a>
|
||||||
|
</p>
|
||||||
{{ template "admin.tmpl" . }}
|
{{ template "admin.tmpl" . }}
|
||||||
{{ else }}
|
{{ else }}
|
||||||
<p>
|
<p>
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Invitea</title>
|
||||||
|
<link rel="stylesheet" href="/css/simple.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<h1>Invitea</h1>
|
||||||
|
<p>
|
||||||
|
Invite Code: <code>{{ .invite.Code }}</code>
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
<form id="create-form" method="POST">
|
||||||
|
<p>
|
||||||
|
<label for="username">Username</label>
|
||||||
|
<input type="text" id="username" name="username"/>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<label for="email">Email</label>
|
||||||
|
<input type="text" id="email" name="email"/>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<label for="password">Password</label>
|
||||||
|
<input type="password" id="password" name="password"/>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<label for="confirm-password">Confirm Password</label>
|
||||||
|
<input type="password" id="confirm-password" name="confirm-password"/>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<button id="create" type="button">Create</button>
|
||||||
|
</form>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
<script>
|
||||||
|
document.getElementById("create").addEventListener("click", () => {
|
||||||
|
const pass1 = document.getElementById("password").value;
|
||||||
|
const pass2 = document.getElementById("confirm-password").value;
|
||||||
|
if (pass1 !== pass2) {
|
||||||
|
alert("Passwords do not match, please re-enter.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
document.getElementById("create-form").submit();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</html>
|
Loading…
Reference in New Issue