internal/control/web.go
99 lines · 3082 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",
19 Examples: []string{"web login"}, Run: runWebLogin})
20 register(Command{Path: []string{"web", "sessions", "list"},
21 Summary: "list your browser sessions",
22 Usage: "web sessions list",
23 Examples: []string{"web sessions list"}, ReadOnly: true, Run: runWebSessionsList})
24 register(Command{Path: []string{"web", "sessions", "revoke"},
25 Summary: "end a browser session, or all of them",
26 Usage: "web sessions revoke <id>|--all",
27 Flags: []Flag{
28 {"--all", "", "revoke every browser session", ""},
29 },
30 Examples: []string{"web sessions revoke --all"}, Run: runWebSessionsRevoke})
31}
32
33func runWebSessionsList(c *Ctx, args []string) int {
34 if len(args) != 0 {
35 return c.usage()
36 }
37 sessions, err := c.Store.ListWebSessions(c.User.ID)
38 if err != nil {
39 return c.fail(protocol.ExitFailure, "%v", err)
40 }
41 return c.emit(sessions, func(w io.Writer) {
42 tb := c.table(w, "ID", "SINCE", "UNTIL")
43 for _, s := range sessions {
44 since, until := s.CreatedAt, s.ExpiresAt
45 if c.Term.Cols == 0 {
46 since, until = stamp(since), stamp(until)
47 } else {
48 since, until = relAge(since, termNow()), relAge(until, termNow())
49 }
50 tb.row(cRef(s.ID), cText("since "+since), cText("until "+until))
51 }
52 tb.flush()
53 })
54}
55
56func runWebSessionsRevoke(c *Ctx, args []string) int {
57 if len(args) != 1 {
58 return c.usage()
59 }
60 if args[0] == "--all" {
61 n, err := c.Store.RevokeAllWebSessions(c.User.ID)
62 if err != nil {
63 return c.fail(protocol.ExitFailure, "%v", err)
64 }
65 return c.emit(map[string]any{"revoked": n}, func(w io.Writer) {
66 fmt.Fprintf(w, "revoked %d browser sessions\n", n)
67 })
68 }
69 if err := c.Store.RevokeWebSession(c.User.ID, args[0]); err != nil {
70 if errors.Is(err, store.ErrNotFound) {
71 return c.fail(protocol.ExitNotFound, "no browser session %s on your account", args[0])
72 }
73 return c.fail(protocol.ExitFailure, "%v", err)
74 }
75 return c.emit(map[string]any{"revoked": args[0]}, func(w io.Writer) {
76 fmt.Fprintf(w, "revoked browser session %s\n", args[0])
77 })
78}
79
80func runWebLogin(c *Ctx, args []string) int {
81 if len(args) != 0 {
82 return c.usage()
83 }
84 if c.Cfg.Web.Mode != "accounts" {
85 return c.fail(protocol.ExitDenied,
86 "this instance runs the web in view-only mode (web.mode = %q); there is nothing to log in to", c.Cfg.Web.Mode)
87 }
88 token, hash, err := newStoredToken()
89 if err != nil {
90 return c.fail(protocol.ExitFailure, "%v", err)
91 }
92 if err := c.Store.CreateLoginToken(c.User.ID, hash, 5*time.Minute); err != nil {
93 return c.fail(protocol.ExitFailure, "%v", err)
94 }
95 url := c.Cfg.Server.SiteURL + "/login?token=" + token
96 return c.emit(map[string]string{"url": url, "expires_in": "5m"}, func(w io.Writer) {
97 fmt.Fprintf(w, "open within 5 minutes (single use):\n%s\n", url)
98 })
99}