package office import ( "context" "fmt" "io" "net/url" "strings" "time" ) type PublicShareAccess struct { Token string FilePath string Password string } func (s *Service) PublicEditorConfig(ctx context.Context, token, filePath, mode, password, guestID, guestName string) (map[string]any, error) { token = strings.TrimSpace(token) filePath = normalizePath(filePath) if token == "" || filePath == "" { return nil, fmt.Errorf("invalid public office session") } if mode == "" { mode = "edit" } rev, err := s.nc.PublicShareFileRevision(ctx, token, filePath, password) if err != nil { return nil, fmt.Errorf("resolve public file revision: %w", err) } apiBase := strings.TrimRight(s.Cfg.APIInternalURL, "/") sig, err := signPublicDocAccess(token, filePath, password, s.Cfg.JWTSecret) if err != nil { return nil, err } downloadURL := buildPublicOfficeEndpointURL(apiBase, token, "/office/document", filePath, password, sig) callbackURL := buildPublicOfficeEndpointURL(apiBase, token, "/office/callback", filePath, password, sig) editorUserID := strings.TrimSpace(guestID) if editorUserID == "" { editorUserID = "public-guest" } else { editorUserID = "public:" + editorUserID } if guestName == "" { guestName = "Invité" } config, err := buildEditorConfig(buildEditorConfigInput{ filePath: filePath, mode: mode, editorUserID: editorUserID, userName: guestName, documentKey: s.keys.current(rev.FileID), downloadURL: downloadURL, callbackURL: callbackURL, }) if err != nil { return nil, err } return wrapConfig(config, s.Cfg.JWTSecret) } func (s *Service) OpenPublicDocument(ctx context.Context, access PublicShareAccess) (io.ReadCloser, string, error) { return s.nc.DownloadPublicShare(ctx, access.Token, access.FilePath, access.Password) } func (s *Service) SavePublicDocument(ctx context.Context, access PublicShareAccess, body io.Reader, contentType string) error { return s.nc.UploadPublicShare(ctx, access.Token, access.FilePath, access.Password, body, contentType) } func buildPublicOfficeEndpointURL(base, token, endpoint, filePath, password, sig string) string { q := url.Values{} q.Set("path", normalizePath(filePath)) if password != "" { q.Set("password", password) } if sig != "" { q.Set("sig", sig) } return strings.TrimRight(base, "/") + "/api/v1/drive/public/shares/" + url.PathEscape(token) + endpoint + "?" + q.Encode() } func signPublicDocAccess(token, filePath, password, secret string) (string, error) { payload := map[string]any{ "token": strings.TrimSpace(token), "path": normalizePath(filePath), "password": password, "exp": time.Now().Add(2 * time.Hour).Unix(), } return signJWT(payload, secret) } func VerifyPublicDocAccess(token, filePath, password, sig, secret string) bool { if secret == "" { return true } payload, err := verifyJWT(sig, secret) if err != nil { return false } if payload["token"] != strings.TrimSpace(token) || payload["path"] != normalizePath(filePath) { return false } if pw, _ := payload["password"].(string); pw != password { return false } if exp, ok := payload["exp"].(float64); ok && int64(exp) < time.Now().Unix() { return false } return true }