quple/server/names.go
Philip Peterson 96f7134c51 Add contacts and per-call Jitsi servers
Everyone signs in at the one central Quine identity server and is called
by username. The caller picks which Jitsi Meet server each call happens
on (meet.jit.si by default, or any other deployment), and the invite
sends the callee to the same one. Jitsi servers need no credentials from
Quple.

- server: POST /call takes an optional jitsiUrl (http(s) only, no
  credentials/query/fragment; default DEFAULT_JITSI) and returns it with
  the meeting ID; invites carry it. Adds GET /me and per-person contacts
  with presence (persisted in DATA_DIR), replacing GET /users.
- desktop: "On server" field for calls, remembered as the last-used
  server; incoming calls show which server they're on; open_meeting
  refuses non-http(s) URLs. meet.jit.si sign-in is now an inline prompt
  when calling there rather than a gate in front of the whole app.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 05:37:31 -08:00

38 lines
1.4 KiB
Go

package main
import (
"errors"
"net/url"
"regexp"
"strings"
)
// usernamePattern matches the identity server's: usernames are what people
// type to call each other, and they end up in URLs and meeting display names.
var usernamePattern = regexp.MustCompile(`^[a-z0-9][a-z0-9._-]{0,31}$`)
// normalizeUsername lowercases s and checks it's a well-formed Quine username.
func normalizeUsername(s string) (string, error) {
s = strings.ToLower(strings.TrimSpace(s))
if !usernamePattern.MatchString(s) {
return "", errors.New("a username is 1-32 chars of a-z, 0-9, '.', '_' or '-', starting with a letter or digit")
}
return s, nil
}
// normalizeJitsiURL checks s is the base URL of a Jitsi Meet deployment, like
// https://meet.jit.si, and returns it without a trailing slash. Any server
// will do - the callee's app opens it, so it only has to be a plain http(s)
// URL that can't smuggle anything else along.
func normalizeJitsiURL(s string) (string, error) {
s = strings.TrimRight(strings.TrimSpace(s), "/")
if !strings.Contains(s, "://") {
s = "https://" + s
}
u, err := url.Parse(s)
if err != nil || (u.Scheme != "https" && u.Scheme != "http") || u.Hostname() == "" ||
u.User != nil || u.RawQuery != "" || u.Fragment != "" || len(s) > 200 {
return "", errors.New("the server must be a Jitsi Meet URL, like https://meet.jit.si")
}
return u.Scheme + "://" + strings.ToLower(u.Host) + u.EscapedPath(), nil
}