session.go (2432B)
1 // session.go — Matrix session persistence (login, save, restore). 2 package main 3 4 import ( 5 "context" 6 "encoding/json" 7 "fmt" 8 "log/slog" 9 10 "maunium.net/go/mautrix" 11 "maunium.net/go/mautrix/id" 12 ) 13 14 type sessionData struct { 15 AccessToken string `json:"access_token"` 16 DeviceID id.DeviceID `json:"device_id"` 17 } 18 19 // ensureSession loads a saved session from S3 or logs in with the account 20 // password and persists the new session for future runs. 21 // 22 // Sessions are stored age-encrypted under s3Key+".age". Plain-JSON sessions 23 // written by older tool versions are transparently read as a fallback so 24 // existing deployments migrate automatically on the next run. 25 func ensureSession(ctx context.Context, client *mautrix.Client, acc accountCfg, s3Key string) (*sessionData, error) { 26 var sess sessionData 27 28 // Try age-encrypted first (current format). 29 if raw, err := getDecryptedAgeFromS3(ctx, s3Key+".age"); err != nil { 30 return nil, fmt.Errorf("load session: %w", err) 31 } else if raw != nil { 32 if err := json.Unmarshal(raw, &sess); err != nil { 33 return nil, fmt.Errorf("parse session: %w", err) 34 } 35 } else { 36 // Fallback: plain JSON written by older versions. 37 if err := s3GetJSON(ctx, s3Key, &sess); err != nil { 38 return nil, fmt.Errorf("load session (legacy): %w", err) 39 } 40 } 41 42 if sess.AccessToken != "" { 43 slog.Info("Restored existing session", "device_id", sess.DeviceID) 44 client.UserID = acc.UserID 45 client.DeviceID = sess.DeviceID 46 client.AccessToken = sess.AccessToken 47 return &sess, nil 48 } 49 50 slog.Info("First run — logging in with password", "user_id", acc.UserID) 51 resp, err := client.Login(ctx, &mautrix.ReqLogin{ 52 Type: mautrix.AuthTypePassword, 53 Identifier: mautrix.UserIdentifier{ 54 Type: mautrix.IdentifierTypeUser, 55 User: string(acc.UserID), 56 }, 57 Password: acc.Password, 58 InitialDeviceDisplayName: "matrix-backup", 59 }) 60 if err != nil { 61 return nil, fmt.Errorf("login: %w", err) 62 } 63 sess = sessionData{ 64 AccessToken: resp.AccessToken, 65 DeviceID: resp.DeviceID, 66 } 67 client.UserID = resp.UserID 68 client.DeviceID = resp.DeviceID 69 client.AccessToken = resp.AccessToken 70 slog.Info("Logged in", "device_id", resp.DeviceID) 71 72 data, err := json.Marshal(sess) 73 if err != nil { 74 return nil, err 75 } 76 // Write age-encrypted; appends ".age" to s3Key. 77 if err := s3PutAge(ctx, s3Key, data); err != nil { 78 return nil, fmt.Errorf("save session: %w", err) 79 } 80 return &sess, nil 81 }