Skip to content

Commit

Permalink
move /users to /user
Browse files Browse the repository at this point in the history
  • Loading branch information
dimkr committed Feb 10, 2025
1 parent 9638ccf commit 2b358d5
Show file tree
Hide file tree
Showing 78 changed files with 1,539 additions and 1,539 deletions.
118 changes: 59 additions & 59 deletions fed/resolve_test.go

Large diffs are not rendered by default.

86 changes: 43 additions & 43 deletions front/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,55 +16,55 @@

Users are authenticated using TLS client certificates; see [Gemini protocol specification](https://gemini.circumlunar.space/docs/specification.html) for more details. The following pages require authentication:

* /users shows posts by followed users, sorted chronologically.
* /users/mentions is like /users but shows only posts that mention the user.
* /users/register creates a new user.
* /users/follows shows a list of followed users, ordered by activity.
* /users/me redirects the user to their outbox.
* /users/resolve looks up federated user *user@domain* or local user *user*.
* /users/dm creates a post visible to mentioned users.
* /users/whisper creates a post visible to followers.
* /users/say creates a public post.
* /users/reply replies to a post.
* /users/edit edits a post.
* /users/delete deletes a post.
* /users/share shares a post.
* /users/unshare removes a shared post.
* /users/follow sends a follow request to a user.
* /users/unfollow deletes a follow request.
* /users/outbox is equivalent to /outbox but also includes a link to /users/follow or /users/unfollow.
* /users/bio allows users to edit their bio.
* /users/name allows users to set their display name.
* /users/alias allows users to set an account alias, to allow migration of accounts to tootik.
* /users/move allows users to notify followers of account migration from tootik.

Some clients generate a certificate for / (all pages of this capsule) when /foo requests a client certificate, while others use the certificate requested by /foo only for /foo and /foo/bar. Therefore, pages that don't require authentication are also mirrored under /users:

* /users/local
* /users/hashtag
* /users/hashtags
* /users/fts
* /users/status
* /users/view
* /users/thread

This way, users who prefer not to provide a client certificate when browsing to /x can reply to public posts by using /users/x instead.

To make the transition to authenticated pages more seamless, links in the user menu at the bottom of each page point to /users/x rather than /x, if the user is authenticated.

All pages follow the [subscription convention](https://gemini.circumlunar.space/docs/companion/subscription.gmi), so users can "subscribe" to a user, a hashtag, posts by followed users or other activity. This way, tootik can act as a personal fediverse aggregator. In addition, feeds like /users have separators between days, to interrupt the endless stream of incoming content, make the content consumption more intentional and prevent doomscrolling.
* /user shows posts by followed users, sorted chronologically.
* /user/mentions is like /user but shows only posts that mention the user.
* /user/register creates a new user.
* /user/follows shows a list of followed users, ordered by activity.
* /user/me redirects the user to their outbox.
* /user/resolve looks up federated user *user@domain* or local user *user*.
* /user/dm creates a post visible to mentioned users.
* /user/whisper creates a post visible to followers.
* /user/say creates a public post.
* /user/reply replies to a post.
* /user/edit edits a post.
* /user/delete deletes a post.
* /user/share shares a post.
* /user/unshare removes a shared post.
* /user/follow sends a follow request to a user.
* /user/unfollow deletes a follow request.
* /user/outbox is equivalent to /outbox but also includes a link to /user/follow or /user/unfollow.
* /user/bio allows users to edit their bio.
* /user/name allows users to set their display name.
* /user/alias allows users to set an account alias, to allow migration of accounts to tootik.
* /user/move allows users to notify followers of account migration from tootik.

Some clients generate a certificate for / (all pages of this capsule) when /foo requests a client certificate, while others use the certificate requested by /foo only for /foo and /foo/bar. Therefore, pages that don't require authentication are also mirrored under /user:

* /user/local
* /user/hashtag
* /user/hashtags
* /user/fts
* /user/status
* /user/view
* /user/thread

This way, users who prefer not to provide a client certificate when browsing to /x can reply to public posts by using /user/x instead.

To make the transition to authenticated pages more seamless, links in the user menu at the bottom of each page point to /user/x rather than /x, if the user is authenticated.

All pages follow the [subscription convention](https://gemini.circumlunar.space/docs/companion/subscription.gmi), so users can "subscribe" to a user, a hashtag, posts by followed users or other activity. This way, tootik can act as a personal fediverse aggregator. In addition, feeds like /user have separators between days, to interrupt the endless stream of incoming content, make the content consumption more intentional and prevent doomscrolling.

## Authentication

If no client certificate is provided, all pages under /users redirect the client to /users.
If no client certificate is provided, all pages under /user redirect the client to /user.

/users asks the client to provide a certificate. Well-behaved clients should generate a certificate, re-request /users, then reuse this certificate in future requests of /users and pages under it.
/user asks the client to provide a certificate. Well-behaved clients should generate a certificate, re-request /user, then reuse this certificate in future requests of /user and pages under it.

If a certificate is provided but does not belong to any user, the client is redirected to /users/register.
If a certificate is provided but does not belong to any user, the client is redirected to /user/register.

By default, the username associated with a client certificate is the common name specified in the certificate. If invalid or already in use by another user, /users/register asks the user to provide a different username. Once the user is registered, the client is redirected back to /users.
By default, the username associated with a client certificate is the common name specified in the certificate. If invalid or already in use by another user, /user/register asks the user to provide a different username. Once the user is registered, the client is redirected back to /user.

Once the client certificate is associated with a user, all pages under /users look up the authenticated user's data using the certificate hash.
Once the client certificate is associated with a user, all pages under /user look up the authenticated user's data using the certificate hash.

## Posts

Expand All @@ -91,7 +91,7 @@ tootik has three kinds of posts:

## Post Editing

/users/edit cannot remove recipients from the post audience, only add more. If a post that mentions only `@a` is edited to mention only `@b`, both `a` and `b` will receive the updated post.
/user/edit cannot remove recipients from the post audience, only add more. If a post that mentions only `@a` is edited to mention only `@b`, both `a` and `b` will receive the updated post.

### Polls

Expand Down
4 changes: 2 additions & 2 deletions front/alias.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import (

func (h *Handler) alias(w text.Writer, r *Request, args ...string) {
if r.User == nil {
w.Redirect("/users")
w.Redirect("/user")
return
}

Expand Down Expand Up @@ -101,5 +101,5 @@ func (h *Handler) alias(w text.Writer, r *Request, args ...string) {
return
}

w.Redirect("/users/outbox/" + strings.TrimPrefix(actor.ID, "https://"))
w.Redirect("/user/outbox/" + strings.TrimPrefix(actor.ID, "https://"))
}
4 changes: 2 additions & 2 deletions front/approve.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import "github.com/dimkr/tootik/front/text"

func (h *Handler) approve(w text.Writer, r *Request, args ...string) {
if r.User == nil {
w.Redirect("/users")
w.Redirect("/user")
return
}

Expand Down Expand Up @@ -50,5 +50,5 @@ func (h *Handler) approve(w text.Writer, r *Request, args ...string) {
return
}

w.Redirect("/users/certificates")
w.Redirect("/user/certificates")
}
4 changes: 2 additions & 2 deletions front/avatar.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ var supportedImageTypes = map[string]struct{}{

func (h *Handler) uploadAvatar(w text.Writer, r *Request, args ...string) {
if r.User == nil || r.Body == nil {
w.Redirectf("gemini://%s/users/oops", h.Domain)
w.Redirectf("gemini://%s/user/oops", h.Domain)
return
}

Expand Down Expand Up @@ -149,5 +149,5 @@ func (h *Handler) uploadAvatar(w text.Writer, r *Request, args ...string) {
return
}

w.Redirectf("gemini://%s/users/outbox/%s", h.Domain, strings.TrimPrefix(r.User.ID, "https://"))
w.Redirectf("gemini://%s/user/outbox/%s", h.Domain, strings.TrimPrefix(r.User.ID, "https://"))
}
4 changes: 2 additions & 2 deletions front/bio.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import (

func (h *Handler) doBio(w text.Writer, r *Request, readInput func(text.Writer, *Request) (string, bool)) {
if r.User == nil {
w.Redirect("/users")
w.Redirect("/user")
return
}

Expand Down Expand Up @@ -86,7 +86,7 @@ func (h *Handler) doBio(w text.Writer, r *Request, readInput func(text.Writer, *
return
}

w.Redirect("/users/outbox/" + strings.TrimPrefix(r.User.ID, "https://"))
w.Redirect("/user/outbox/" + strings.TrimPrefix(r.User.ID, "https://"))
}

func (h *Handler) bio(w text.Writer, r *Request, args ...string) {
Expand Down
4 changes: 2 additions & 2 deletions front/bookmark.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import (

func (h *Handler) bookmark(w text.Writer, r *Request, args ...string) {
if r.User == nil {
w.Redirect("/users")
w.Redirect("/user")
return
}

Expand Down Expand Up @@ -105,5 +105,5 @@ func (h *Handler) bookmark(w text.Writer, r *Request, args ...string) {
return
}

w.Redirectf("/users/view/" + args[1])
w.Redirectf("/user/view/" + args[1])
}
8 changes: 4 additions & 4 deletions front/certificates.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import (

func (h *Handler) certificates(w text.Writer, r *Request, args ...string) {
if r.User == nil {
w.Redirect("/users")
w.Redirect("/user")
return
}

Expand Down Expand Up @@ -67,10 +67,10 @@ func (h *Handler) certificates(w text.Writer, r *Request, args ...string) {
w.Item("Expires: " + time.Unix(expires, 0).Format(time.DateOnly))

if approved == 0 {
w.Link("/users/certificates/approve/"+hash, "🟢 Approve")
w.Link("/users/certificates/revoke/"+hash, "🔴 Deny")
w.Link("/user/certificates/approve/"+hash, "🟢 Approve")
w.Link("/user/certificates/revoke/"+hash, "🔴 Deny")
} else {
w.Link("/users/certificates/revoke/"+hash, "🔴 Revoke")
w.Link("/user/certificates/revoke/"+hash, "🔴 Revoke")
}

first = false
Expand Down
2 changes: 1 addition & 1 deletion front/communities.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ func (h *Handler) communities(w text.Writer, r *Request, args ...string) {
if r.User == nil {
w.Linkf("/outbox/"+strings.TrimPrefix(id, "https://"), "%s %s", time.Unix(last, 0).Format(time.DateOnly), username)
} else {
w.Linkf("/users/outbox/"+strings.TrimPrefix(id, "https://"), "%s %s", time.Unix(last, 0).Format(time.DateOnly), username)
w.Linkf("/user/outbox/"+strings.TrimPrefix(id, "https://"), "%s %s", time.Unix(last, 0).Format(time.DateOnly), username)
}

empty = false
Expand Down
4 changes: 2 additions & 2 deletions front/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import (

func (h *Handler) delete(w text.Writer, r *Request, args ...string) {
if r.User == nil {
w.Redirect("/users")
w.Redirect("/user")
return
}

Expand All @@ -51,5 +51,5 @@ func (h *Handler) delete(w text.Writer, r *Request, args ...string) {
return
}

w.Redirect("/users/outbox/" + strings.TrimPrefix(r.User.ID, "https://"))
w.Redirect("/user/outbox/" + strings.TrimPrefix(r.User.ID, "https://"))
}
4 changes: 2 additions & 2 deletions front/dm.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import (

func (h *Handler) dm(w text.Writer, r *Request, args ...string) {
if r.User == nil {
w.Redirect("/users")
w.Redirect("/user")
return
}

Expand All @@ -37,7 +37,7 @@ func (h *Handler) dm(w text.Writer, r *Request, args ...string) {

func (h *Handler) uploadDM(w text.Writer, r *Request, args ...string) {
if r.User == nil {
w.Redirect("/users")
w.Redirect("/user")
return
}

Expand Down
2 changes: 1 addition & 1 deletion front/edit.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import (

func (h *Handler) doEdit(w text.Writer, r *Request, args []string, readInput inputFunc) {
if r.User == nil {
w.Redirect("/users")
w.Redirect("/user")
return
}

Expand Down
2 changes: 1 addition & 1 deletion front/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ var csvHeader = []string{"ID", "Type", "Inserted", "Activity"}

func (h *Handler) export(w text.Writer, r *Request, args ...string) {
if r.User == nil {
w.Redirect("/users")
w.Redirect("/user")
return
}

Expand Down
4 changes: 2 additions & 2 deletions front/follow.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import (

func (h *Handler) follow(w text.Writer, r *Request, args ...string) {
if r.User == nil {
w.Redirect("/users")
w.Redirect("/user")
return
}

Expand Down Expand Up @@ -71,5 +71,5 @@ func (h *Handler) follow(w text.Writer, r *Request, args ...string) {
return
}

w.Redirectf("/users/outbox/" + args[1])
w.Redirectf("/user/outbox/" + args[1])
}
6 changes: 3 additions & 3 deletions front/follows.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import (

func (h *Handler) follows(w text.Writer, r *Request, args ...string) {
if r.User == nil {
w.Redirect("/users")
w.Redirect("/user")
return
}

Expand Down Expand Up @@ -91,9 +91,9 @@ func (h *Handler) follows(w text.Writer, r *Request, args ...string) {
displayName := h.getActorDisplayName(&actor)

if last.Valid {
w.Linkf("/users/outbox/"+strings.TrimPrefix(actor.ID, "https://"), "%s %s", time.Unix(last.Int64*(60*60*24), 0).Format(time.DateOnly), displayName)
w.Linkf("/user/outbox/"+strings.TrimPrefix(actor.ID, "https://"), "%s %s", time.Unix(last.Int64*(60*60*24), 0).Format(time.DateOnly), displayName)
} else {
w.Link("/users/outbox/"+strings.TrimPrefix(actor.ID, "https://"), displayName)
w.Link("/user/outbox/"+strings.TrimPrefix(actor.ID, "https://"), displayName)
}

i++
Expand Down
8 changes: 4 additions & 4 deletions front/gemini/gemini.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,9 @@ func (gl *Listener) Handle(ctx context.Context, conn net.Conn) {
defer w.Flush()

r.User, r.Key, err = gl.getUser(ctx, tlsConn)
if err != nil && errors.Is(err, front.ErrNotRegistered) && r.URL.Path == "/users" {
if err != nil && errors.Is(err, front.ErrNotRegistered) && r.URL.Path == "/user" {
slog.Info("Redirecting new user")
w.Redirect("/users/register")
w.Redirect("/user/register")
return
} else if errors.Is(err, front.ErrNotApproved) {
w.Status(40, "Client certificate is awaiting approval")
Expand All @@ -158,10 +158,10 @@ func (gl *Listener) Handle(ctx context.Context, conn net.Conn) {
slog.Warn("Failed to get user", "error", err)
w.Error()
return
} else if err == nil && r.User == nil && r.URL.Path == "/users" {
} else if err == nil && r.User == nil && r.URL.Path == "/user" {
w.Status(60, "Client certificate required")
return
} else if r.User == nil && gl.Config.RequireRegistration && r.URL.Path != "/" && r.URL.Path != "/help" && r.URL.Path != "/users/register" {
} else if r.User == nil && gl.Config.RequireRegistration && r.URL.Path != "/" && r.URL.Path != "/help" && r.URL.Path != "/user/register" {
w.Status(40, "Must register first")
return
}
Expand Down
Loading

0 comments on commit 2b358d5

Please sign in to comment.