internal/control/web.go

2867b44a48ea8afa6b7c21d5e1d69272a7b235fa
gitbay/internal/control/web.go history · blame · raw

85 lines · 2804 bytes

 1package control
 2
 3import (
 4	"errors"
 5	"fmt"
 6	"io"
 7	"time"
 8
 9	"gitbay.org/gitbay/internal/protocol"
10	"gitbay.org/gitbay/internal/store"
11)
12
13func newStoredToken() (token, hash string, err error) { return store.NewToken() }
14
15func init() {
16	register(Command{Path: []string{"web", "login"},
17		Summary: "mint a one-time browser login URL",
18		Usage:   "web login", Run: runWebLogin})
19	register(Command{Path: []string{"web", "sessions", "list"},
20		Summary: "list your browser sessions",
21		Usage:   "web sessions list", ReadOnly: true, SSHOnly: true, Run: runWebSessionsList})
22	register(Command{Path: []string{"web", "sessions", "revoke"},
23		Summary: "end a browser session, or all of them",
24		Usage:   "web sessions revoke <id>|--all", SSHOnly: true, Run: runWebSessionsRevoke})
25}
26
27func runWebSessionsList(c *Ctx, args []string) int {
28	if len(args) != 0 {
29		return c.fail(protocol.ExitUsage, "usage: web sessions list")
30	}
31	sessions, err := c.Store.ListWebSessions(c.User.ID)
32	if err != nil {
33		return c.fail(protocol.ExitFailure, "%v", err)
34	}
35	return c.emit(sessions, func(w io.Writer) {
36		for _, s := range sessions {
37			fmt.Fprintf(w, "%s\tsince %s\tuntil %s\n", s.ID, s.CreatedAt, s.ExpiresAt)
38		}
39	})
40}
41
42func runWebSessionsRevoke(c *Ctx, args []string) int {
43	if len(args) != 1 {
44		return c.fail(protocol.ExitUsage, "usage: web sessions revoke <id>|--all")
45	}
46	if args[0] == "--all" {
47		n, err := c.Store.RevokeAllWebSessions(c.User.ID)
48		if err != nil {
49			return c.fail(protocol.ExitFailure, "%v", err)
50		}
51		return c.emit(map[string]any{"revoked": n}, func(w io.Writer) {
52			fmt.Fprintf(w, "revoked %d browser sessions\n", n)
53		})
54	}
55	if err := c.Store.RevokeWebSession(c.User.ID, args[0]); err != nil {
56		if errors.Is(err, store.ErrNotFound) {
57			return c.fail(protocol.ExitNotFound, "no browser session %s on your account", args[0])
58		}
59		return c.fail(protocol.ExitFailure, "%v", err)
60	}
61	return c.emit(map[string]any{"revoked": args[0]}, func(w io.Writer) {
62		fmt.Fprintf(w, "revoked browser session %s\n", args[0])
63	})
64}
65
66func runWebLogin(c *Ctx, args []string) int {
67	if len(args) != 0 {
68		return c.fail(protocol.ExitUsage, "usage: web login [--json]")
69	}
70	if c.Cfg.Web.Mode != "accounts" {
71		return c.fail(protocol.ExitDenied,
72			"this instance runs the web in view-only mode (web.mode = %q); there is nothing to log in to", c.Cfg.Web.Mode)
73	}
74	token, hash, err := newStoredToken()
75	if err != nil {
76		return c.fail(protocol.ExitFailure, "%v", err)
77	}
78	if err := c.Store.CreateLoginToken(c.User.ID, hash, 5*time.Minute); err != nil {
79		return c.fail(protocol.ExitFailure, "%v", err)
80	}
81	url := c.Cfg.Server.SiteURL + "/login?token=" + token
82	return c.emit(map[string]string{"url": url, "expires_in": "5m"}, func(w io.Writer) {
83		fmt.Fprintf(w, "open within 5 minutes (single use):\n%s\n", url)
84	})
85}