updates
Server Deploy / deploy (push) Successful in 2m50s

This commit is contained in:
2026-07-24 09:24:03 +01:00
parent 693d59a3e2
commit 3b52bcbeb8
15 changed files with 527 additions and 588 deletions
+4 -2
View File
@@ -10,6 +10,7 @@ import (
"syscall"
"time"
"github.com/joho/godotenv"
"github.com/mrhid6/vantage/sitesvc/internal/api"
"github.com/mrhid6/vantage/sitesvc/internal/mail"
"github.com/mrhid6/vantage/sitesvc/internal/store"
@@ -21,8 +22,9 @@ import (
// that is how the new tenant becomes visible to the app — but shares no code
// and no process with it.
func main() {
// The database name comes from the URI path, e.g.
// mongodb://user:pass@host:27017/vantage?authSource=vantage
godotenv.Load()
mongoURI := getEnv("MONGO_URI", "mongodb://localhost:27017/vantage")
addr := ":" + getEnv("PORT", "8082")
+1
View File
@@ -4,6 +4,7 @@ go 1.26
require (
github.com/google/uuid v1.6.0
github.com/joho/godotenv v1.5.1
go.mongodb.org/mongo-driver/v2 v2.2.2
golang.org/x/crypto v0.54.0
)
+2
View File
@@ -6,6 +6,8 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI=
github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
+23
View File
@@ -1,7 +1,9 @@
package mail
import (
"crypto/rand"
"crypto/tls"
"encoding/hex"
"fmt"
"mime"
"net"
@@ -135,6 +137,12 @@ func message(from, to, subject, body, replyTo string) []byte {
if replyTo != "" {
b.WriteString("Reply-To: " + sanitizeHeader(replyTo) + "\r\n")
}
// Date and Message-ID are RFC 5322 essentials. Without them many servers
// accept the message at SMTP time and then silently junk or drop it, and
// SpamAssassin scores MISSING_DATE and MISSING_MID heavily — the message
// "sends" but never lands in the inbox.
b.WriteString("Date: " + time.Now().Format(time.RFC1123Z) + "\r\n")
b.WriteString("Message-ID: " + messageID(from) + "\r\n")
b.WriteString("Subject: " + mime.QEncoding.Encode("utf-8", sanitizeHeader(subject)) + "\r\n")
b.WriteString("MIME-Version: 1.0\r\n")
b.WriteString("Content-Type: text/plain; charset=utf-8\r\n")
@@ -143,6 +151,21 @@ func message(from, to, subject, body, replyTo string) []byte {
return []byte(b.String())
}
// messageID builds a unique <id@domain>, taking the domain from the From
// address so the identifier matches the sending domain. Falls back to the host
// name when From has no domain part.
func messageID(from string) string {
domain := "vantage.local"
if at := strings.LastIndex(from, "@"); at >= 0 && at < len(from)-1 {
domain = strings.Trim(from[at+1:], "<> ")
}
var buf [16]byte
if _, err := rand.Read(buf[:]); err != nil {
return fmt.Sprintf("<%d@%s>", time.Now().UnixNano(), domain)
}
return fmt.Sprintf("<%s@%s>", hex.EncodeToString(buf[:]), domain)
}
func sanitizeHeader(v string) string {
return strings.NewReplacer("\r", " ", "\n", " ").Replace(v)
}
-14
View File
@@ -74,14 +74,6 @@ func DatabaseName() string {
func col(name string) *mongo.Collection { return database.Collection(name) }
// EnsureIndexes builds the constraints sitesvc depends on.
//
// The unique indexes on users.email and orgs.slug are the same ones the control
// plane builds at boot, and they are a security property rather than an
// optimisation: without them a duplicate email lets an unscoped user lookup
// match the wrong account, and a duplicate slug makes host-based org resolution
// pick one at random. They are (re)declared here so sitesvc does not depend on
// the server having started first. Creating an existing index is a no-op.
func EnsureIndexes() error {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
@@ -119,17 +111,11 @@ func EnsureIndexes() error {
return nil
}
// EmailTaken reports whether an address already has an account. It is a
// courtesy check for a clear error message; the unique index is what actually
// enforces uniqueness at verification time.
func EmailTaken(ctx context.Context, email string) (bool, error) {
n, err := col("users").CountDocuments(ctx, bson.M{"email": email})
return n > 0, err
}
// CreatePending stores an unverified signup and returns the raw token for the
// email link. Only the token's SHA-256 hash is persisted, so a leaked database
// does not yield working verification links.
func CreatePending(ctx context.Context, orgName, email, password string) (string, error) {
if _, err := provision.BaseSlug(orgName); err != nil {
return "", fmt.Errorf("%w: %s", ErrNameRejected, err.Error())