package authsession import ( "bytes" "context" "crypto/ed25519" "encoding/base64" "encoding/json" "fmt" "io" "net" "net/http" "strconv" "strings" "testing" "time" "galaxy/authsession/internal/adapters/mail" "galaxy/authsession/internal/adapters/redis/challengestore" "galaxy/authsession/internal/adapters/redis/configprovider" "galaxy/authsession/internal/adapters/redis/projectionpublisher" "galaxy/authsession/internal/adapters/redis/sessionstore" "galaxy/authsession/internal/adapters/userservice" "galaxy/authsession/internal/api/internalhttp" "galaxy/authsession/internal/api/publichttp" "galaxy/authsession/internal/domain/common" "galaxy/authsession/internal/domain/devicesession" "galaxy/authsession/internal/service/blockuser" "galaxy/authsession/internal/service/confirmemailcode" "galaxy/authsession/internal/service/getsession" "galaxy/authsession/internal/service/listusersessions" "galaxy/authsession/internal/service/revokeallusersessions" "galaxy/authsession/internal/service/revokedevicesession" "galaxy/authsession/internal/service/sendemailcode" "galaxy/authsession/internal/testkit" "github.com/alicebob/miniredis/v2" "github.com/redis/go-redis/v9" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap" ) const ( gatewayCompatibilityChallengeKeyPrefix = "authsession:challenge:" gatewayCompatibilitySessionKeyPrefix = "authsession:session:" gatewayCompatibilityUserSessionsKeyPrefix = "authsession:user-sessions:" gatewayCompatibilityUserActiveKeyPrefix = "authsession:user-active-sessions:" gatewayCompatibilitySessionLimitKey = "authsession:config:active-session-limit" gatewayCompatibilitySessionCacheKeyPrefix = "gateway:session:" gatewayCompatibilitySessionEventsStream = "gateway:session_events" gatewayCompatibilityStreamMaxLen int64 = 128 gatewayCompatibilityEmail = "pilot@example.com" gatewayCompatibilityCode = "123456" gatewayCompatibilityTimeZone = "Europe/Kaliningrad" ) var gatewayCompatibilityClientPublicKey = mustGatewayCompatibilityClientPublicKeyBase64() func gatewayCompatibilityConfirmRequest(challengeID string, code string, clientPublicKey string) map[string]string { return map[string]string{ "challenge_id": challengeID, "code": code, "client_public_key": clientPublicKey, "time_zone": gatewayCompatibilityTimeZone, } } func TestGatewayCompatibilityConfirmReturnsGatewayReadableSessionProjection(t *testing.T) { t.Parallel() app := newGatewayCompatibilityHarness(t, gatewayCompatibilityOptions{}) sendResponse := gatewayCompatibilityPostJSON(t, app.publicBaseURL+"/api/v1/public/auth/send-email-code", `{"email":"pilot@example.com"}`) assert.Equal(t, http.StatusOK, sendResponse.StatusCode) var sendBody struct { ChallengeID string `json:"challenge_id"` } require.NoError(t, json.Unmarshal([]byte(sendResponse.Body), &sendBody)) assert.Equal(t, "challenge-1", sendBody.ChallengeID) attempts := app.mailSender.RecordedAttempts() require.Len(t, attempts, 1) confirmResponse := gatewayCompatibilityPostJSONValue( t, app.publicBaseURL+"/api/v1/public/auth/confirm-email-code", gatewayCompatibilityConfirmRequest(sendBody.ChallengeID, attempts[0].Input.Code, gatewayCompatibilityClientPublicKey), ) assert.Equal(t, http.StatusOK, confirmResponse.StatusCode) var confirmBody struct { DeviceSessionID string `json:"device_session_id"` } require.NoError(t, json.Unmarshal([]byte(confirmResponse.Body), &confirmBody)) assert.Equal(t, "device-session-1", confirmBody.DeviceSessionID) record := app.mustReadGatewayCacheRecord(t, confirmBody.DeviceSessionID) assert.Equal(t, gatewayCacheRecord{ DeviceSessionID: "device-session-1", UserID: "user-1", ClientPublicKey: gatewayCompatibilityClientPublicKey, Status: "active", }, record) events := app.mustReadGatewaySessionEvents(t, confirmBody.DeviceSessionID) require.NotEmpty(t, events) assert.Equal(t, gatewaySessionEventRecord{ DeviceSessionID: "device-session-1", UserID: "user-1", ClientPublicKey: gatewayCompatibilityClientPublicKey, Status: "active", }, events[len(events)-1]) } func TestGatewayCompatibilityRevokePublishesRevokedGatewayProjection(t *testing.T) { t.Parallel() app := newGatewayCompatibilityHarness(t, gatewayCompatibilityOptions{}) sessionID := app.createSessionThroughPublicFlow(t) revokeResponse := gatewayCompatibilityPostJSON( t, app.internalBaseURL+"/api/v1/internal/sessions/"+sessionID+"/revoke", `{"reason_code":"admin_revoke","actor":{"type":"system"}}`, ) assert.Equal(t, http.StatusOK, revokeResponse.StatusCode) assert.JSONEq(t, `{"outcome":"revoked","device_session_id":"`+sessionID+`","affected_session_count":1}`, revokeResponse.Body) record := app.mustReadGatewayCacheRecord(t, sessionID) require.NotNil(t, record.RevokedAtMS) assert.Equal(t, gatewayCacheRecord{ DeviceSessionID: sessionID, UserID: "user-1", ClientPublicKey: gatewayCompatibilityClientPublicKey, Status: "revoked", RevokedAtMS: int64Pointer(app.now.UnixMilli()), }, record) events := app.mustReadGatewaySessionEvents(t, sessionID) require.NotEmpty(t, events) last := events[len(events)-1] require.NotNil(t, last.RevokedAtMS) assert.Equal(t, gatewaySessionEventRecord{ DeviceSessionID: sessionID, UserID: "user-1", ClientPublicKey: gatewayCompatibilityClientPublicKey, Status: "revoked", RevokedAtMS: int64Pointer(app.now.UnixMilli()), }, last) } func TestGatewayCompatibilityRepeatedConfirmReturnsSameSessionID(t *testing.T) { t.Parallel() app := newGatewayCompatibilityHarness(t, gatewayCompatibilityOptions{}) sendResponse := gatewayCompatibilityPostJSON(t, app.publicBaseURL+"/api/v1/public/auth/send-email-code", `{"email":"pilot@example.com"}`) assert.Equal(t, http.StatusOK, sendResponse.StatusCode) var sendBody struct { ChallengeID string `json:"challenge_id"` } require.NoError(t, json.Unmarshal([]byte(sendResponse.Body), &sendBody)) attempts := app.mailSender.RecordedAttempts() require.Len(t, attempts, 1) requestBody := gatewayCompatibilityConfirmRequest(sendBody.ChallengeID, attempts[0].Input.Code, gatewayCompatibilityClientPublicKey) first := gatewayCompatibilityPostJSONValue(t, app.publicBaseURL+"/api/v1/public/auth/confirm-email-code", requestBody) second := gatewayCompatibilityPostJSONValue(t, app.publicBaseURL+"/api/v1/public/auth/confirm-email-code", requestBody) assert.Equal(t, http.StatusOK, first.StatusCode) assert.Equal(t, http.StatusOK, second.StatusCode) var firstBody struct { DeviceSessionID string `json:"device_session_id"` } var secondBody struct { DeviceSessionID string `json:"device_session_id"` } require.NoError(t, json.Unmarshal([]byte(first.Body), &firstBody)) require.NoError(t, json.Unmarshal([]byte(second.Body), &secondBody)) assert.Equal(t, firstBody.DeviceSessionID, secondBody.DeviceSessionID) record := app.mustReadGatewayCacheRecord(t, firstBody.DeviceSessionID) assert.Equal(t, gatewayCacheRecord{ DeviceSessionID: firstBody.DeviceSessionID, UserID: "user-1", ClientPublicKey: gatewayCompatibilityClientPublicKey, Status: "active", }, record) } func TestGatewayCompatibilityBlockedEmailSendRemainsSuccessShaped(t *testing.T) { t.Parallel() app := newGatewayCompatibilityHarness(t, gatewayCompatibilityOptions{ SeedBlockedEmail: true, }) response := gatewayCompatibilityPostJSON(t, app.publicBaseURL+"/api/v1/public/auth/send-email-code", `{"email":"pilot@example.com"}`) assert.Equal(t, http.StatusOK, response.StatusCode) var body map[string]string require.NoError(t, json.Unmarshal([]byte(response.Body), &body)) assert.Equal(t, map[string]string{"challenge_id": "challenge-1"}, body) } func TestGatewayCompatibilitySessionLimitExceededReturnsStableClientError(t *testing.T) { t.Parallel() limit := 1 app := newGatewayCompatibilityHarness(t, gatewayCompatibilityOptions{ SeedExistingUser: true, SessionLimit: &limit, SeedActiveSessions: []devicesession.Session{ gatewayCompatibilityActiveSession( t, "device-session-existing", "user-1", gatewayCompatibilityClientPublicKey, time.Date(2026, 4, 5, 11, 58, 0, 0, time.UTC), ), }, }) sendResponse := gatewayCompatibilityPostJSON(t, app.publicBaseURL+"/api/v1/public/auth/send-email-code", `{"email":"pilot@example.com"}`) assert.Equal(t, http.StatusOK, sendResponse.StatusCode) attempts := app.mailSender.RecordedAttempts() require.Len(t, attempts, 1) confirmResponse := gatewayCompatibilityPostJSONValue( t, app.publicBaseURL+"/api/v1/public/auth/confirm-email-code", gatewayCompatibilityConfirmRequest("challenge-1", attempts[0].Input.Code, gatewayCompatibilityClientPublicKey), ) assert.Equal(t, http.StatusConflict, confirmResponse.StatusCode) assert.JSONEq(t, `{"error":{"code":"session_limit_exceeded","message":"active session limit would be exceeded"}}`, confirmResponse.Body) } func TestGatewayCompatibilityMalformedClientPublicKeyReturnsStableError(t *testing.T) { t.Parallel() app := newGatewayCompatibilityHarness(t, gatewayCompatibilityOptions{}) response := gatewayCompatibilityPostJSON( t, app.publicBaseURL+"/api/v1/public/auth/confirm-email-code", `{"challenge_id":"challenge-123","code":"123456","client_public_key":"invalid","time_zone":"`+gatewayCompatibilityTimeZone+`"}`, ) assert.Equal(t, http.StatusBadRequest, response.StatusCode) assert.JSONEq(t, `{"error":{"code":"invalid_client_public_key","message":"client_public_key is not a valid base64-encoded raw 32-byte Ed25519 public key"}}`, response.Body) } type gatewayCompatibilityOptions struct { SeedBlockedEmail bool SeedExistingUser bool SessionLimit *int SeedActiveSessions []devicesession.Session } // gatewayCompatibilityHarness owns one gateway-focused integration test setup // with real HTTP servers and real Redis-backed authsession adapters. type gatewayCompatibilityHarness struct { publicBaseURL string internalBaseURL string mailSender *mail.StubSender redisClient *redis.Client now time.Time } func newGatewayCompatibilityHarness(t *testing.T, options gatewayCompatibilityOptions) gatewayCompatibilityHarness { t.Helper() now := time.Date(2026, 4, 5, 12, 0, 0, 0, time.UTC) redisServer := miniredis.RunT(t) redisClient := redis.NewClient(&redis.Options{ Addr: redisServer.Addr(), Protocol: 2, DisableIdentity: true, }) t.Cleanup(func() { assert.NoError(t, redisClient.Close()) }) if options.SessionLimit != nil { redisServer.Set(gatewayCompatibilitySessionLimitKey, strconv.Itoa(*options.SessionLimit)) } challengeStore, err := challengestore.New(challengestore.Config{ Addr: redisServer.Addr(), DB: 0, KeyPrefix: gatewayCompatibilityChallengeKeyPrefix, OperationTimeout: 250 * time.Millisecond, }) require.NoError(t, err) t.Cleanup(func() { assert.NoError(t, challengeStore.Close()) }) sessionStore, err := sessionstore.New(sessionstore.Config{ Addr: redisServer.Addr(), DB: 0, SessionKeyPrefix: gatewayCompatibilitySessionKeyPrefix, UserSessionsKeyPrefix: gatewayCompatibilityUserSessionsKeyPrefix, UserActiveSessionsKeyPrefix: gatewayCompatibilityUserActiveKeyPrefix, OperationTimeout: 250 * time.Millisecond, }) require.NoError(t, err) t.Cleanup(func() { assert.NoError(t, sessionStore.Close()) }) configStore, err := configprovider.New(configprovider.Config{ Addr: redisServer.Addr(), DB: 0, SessionLimitKey: gatewayCompatibilitySessionLimitKey, OperationTimeout: 250 * time.Millisecond, }) require.NoError(t, err) t.Cleanup(func() { assert.NoError(t, configStore.Close()) }) publisher, err := projectionpublisher.New(projectionpublisher.Config{ Addr: redisServer.Addr(), DB: 0, SessionCacheKeyPrefix: gatewayCompatibilitySessionCacheKeyPrefix, SessionEventsStream: gatewayCompatibilitySessionEventsStream, StreamMaxLen: gatewayCompatibilityStreamMaxLen, OperationTimeout: 250 * time.Millisecond, }) require.NoError(t, err) t.Cleanup(func() { assert.NoError(t, publisher.Close()) }) userDirectory := &userservice.StubDirectory{} if options.SeedBlockedEmail { require.NoError(t, userDirectory.SeedBlockedEmail(common.Email(gatewayCompatibilityEmail), "policy_blocked")) } if options.SeedExistingUser { require.NoError(t, userDirectory.SeedExisting(common.Email(gatewayCompatibilityEmail), common.UserID("user-1"))) } for _, session := range options.SeedActiveSessions { require.NoError(t, sessionStore.Create(context.Background(), session)) } mailSender := &mail.StubSender{} idGenerator := &testkit.SequenceIDGenerator{} codeGenerator := testkit.FixedCodeGenerator{Code: gatewayCompatibilityCode} codeHasher := testkit.DeterministicCodeHasher{} clock := testkit.FixedClock{Time: now} sendEmailCodeService, err := sendemailcode.NewWithObservability( challengeStore, userDirectory, idGenerator, codeGenerator, codeHasher, mailSender, nil, clock, zap.NewNop(), nil, ) require.NoError(t, err) confirmEmailCodeService, err := confirmemailcode.NewWithObservability( challengeStore, sessionStore, userDirectory, configStore, publisher, idGenerator, codeHasher, clock, zap.NewNop(), nil, ) require.NoError(t, err) getSessionService, err := getsession.New(sessionStore) require.NoError(t, err) listUserSessionsService, err := listusersessions.New(sessionStore) require.NoError(t, err) revokeDeviceSessionService, err := revokedevicesession.NewWithObservability(sessionStore, publisher, clock, zap.NewNop(), nil) require.NoError(t, err) revokeAllUserSessionsService, err := revokeallusersessions.NewWithObservability(sessionStore, userDirectory, publisher, clock, zap.NewNop(), nil) require.NoError(t, err) blockUserService, err := blockuser.NewWithObservability(userDirectory, sessionStore, publisher, clock, zap.NewNop(), nil) require.NoError(t, err) publicCfg := publichttp.DefaultConfig() publicCfg.Addr = gatewayCompatibilityFreeAddr(t) publicServer, err := publichttp.NewServer(publicCfg, publichttp.Dependencies{ SendEmailCode: sendEmailCodeService, ConfirmEmailCode: confirmEmailCodeService, Logger: zap.NewNop(), }) require.NoError(t, err) internalCfg := internalhttp.DefaultConfig() internalCfg.Addr = gatewayCompatibilityFreeAddr(t) internalServer, err := internalhttp.NewServer(internalCfg, internalhttp.Dependencies{ GetSession: getSessionService, ListUserSessions: listUserSessionsService, RevokeDeviceSession: revokeDeviceSessionService, RevokeAllUserSessions: revokeAllUserSessionsService, BlockUser: blockUserService, Logger: zap.NewNop(), }) require.NoError(t, err) gatewayCompatibilityRunServer(t, publicServer.Run, publicServer.Shutdown, publicCfg.Addr) gatewayCompatibilityRunServer(t, internalServer.Run, internalServer.Shutdown, internalCfg.Addr) return gatewayCompatibilityHarness{ publicBaseURL: "http://" + publicCfg.Addr, internalBaseURL: "http://" + internalCfg.Addr, mailSender: mailSender, redisClient: redisClient, now: now, } } func (h gatewayCompatibilityHarness) createSessionThroughPublicFlow(t *testing.T) string { t.Helper() sendResponse := gatewayCompatibilityPostJSON(t, h.publicBaseURL+"/api/v1/public/auth/send-email-code", `{"email":"pilot@example.com"}`) assert.Equal(t, http.StatusOK, sendResponse.StatusCode) var sendBody struct { ChallengeID string `json:"challenge_id"` } require.NoError(t, json.Unmarshal([]byte(sendResponse.Body), &sendBody)) attempts := h.mailSender.RecordedAttempts() require.Len(t, attempts, 1) confirmResponse := gatewayCompatibilityPostJSONValue( t, h.publicBaseURL+"/api/v1/public/auth/confirm-email-code", gatewayCompatibilityConfirmRequest(sendBody.ChallengeID, attempts[0].Input.Code, gatewayCompatibilityClientPublicKey), ) assert.Equal(t, http.StatusOK, confirmResponse.StatusCode) var confirmBody struct { DeviceSessionID string `json:"device_session_id"` } require.NoError(t, json.Unmarshal([]byte(confirmResponse.Body), &confirmBody)) return confirmBody.DeviceSessionID } // gatewayCacheRecord mirrors the strict gateway Redis session-cache wire // contract used on the authenticated hot path. type gatewayCacheRecord struct { DeviceSessionID string `json:"device_session_id"` UserID string `json:"user_id"` ClientPublicKey string `json:"client_public_key"` Status string `json:"status"` RevokedAtMS *int64 `json:"revoked_at_ms,omitempty"` } func (h gatewayCompatibilityHarness) mustReadGatewayCacheRecord(t *testing.T, deviceSessionID string) gatewayCacheRecord { t.Helper() payload, err := h.redisClient.Get(context.Background(), gatewayCompatibilitySessionCacheKeyPrefix+deviceSessionID).Bytes() require.NoError(t, err) decoder := json.NewDecoder(bytes.NewReader(payload)) decoder.DisallowUnknownFields() var record gatewayCacheRecord require.NoError(t, decoder.Decode(&record)) err = decoder.Decode(&struct{}{}) require.ErrorIs(t, err, io.EOF) require.NotEmpty(t, record.DeviceSessionID) require.Equal(t, deviceSessionID, record.DeviceSessionID) require.NotEmpty(t, record.UserID) require.NotEmpty(t, record.ClientPublicKey) require.Contains(t, []string{"active", "revoked"}, record.Status) return record } // gatewaySessionEventRecord mirrors the strict gateway Redis Stream event // contract for full session snapshots. type gatewaySessionEventRecord struct { DeviceSessionID string UserID string ClientPublicKey string Status string RevokedAtMS *int64 } func (h gatewayCompatibilityHarness) mustReadGatewaySessionEvents(t *testing.T, deviceSessionID string) []gatewaySessionEventRecord { t.Helper() entries, err := h.redisClient.XRange(context.Background(), gatewayCompatibilitySessionEventsStream, "-", "+").Result() require.NoError(t, err) records := make([]gatewaySessionEventRecord, 0, len(entries)) for _, entry := range entries { record := decodeGatewaySessionEvent(t, entry.Values) if record.DeviceSessionID == deviceSessionID { records = append(records, record) } } require.NotEmpty(t, records) return records } func decodeGatewaySessionEvent(t *testing.T, values map[string]any) gatewaySessionEventRecord { t.Helper() requiredKeys := map[string]struct{}{ "device_session_id": {}, "user_id": {}, "client_public_key": {}, "status": {}, } optionalKeys := map[string]struct{}{ "revoked_at_ms": {}, } for key := range values { if _, ok := requiredKeys[key]; ok { continue } if _, ok := optionalKeys[key]; ok { continue } require.Failf(t, "test failed", "decode gateway session event: unsupported field %q", key) } record := gatewaySessionEventRecord{ DeviceSessionID: gatewayCompatibilityRequiredStringField(t, values, "device_session_id"), UserID: gatewayCompatibilityRequiredStringField(t, values, "user_id"), ClientPublicKey: gatewayCompatibilityRequiredStringField(t, values, "client_public_key"), Status: gatewayCompatibilityRequiredStringField(t, values, "status"), } require.Contains(t, []string{"active", "revoked"}, record.Status) if rawRevokedAtMS, ok := values["revoked_at_ms"]; ok { parsed := gatewayCompatibilityParseInt64Field(t, rawRevokedAtMS, "revoked_at_ms") record.RevokedAtMS = &parsed } return record } func gatewayCompatibilityRequiredStringField(t *testing.T, values map[string]any, field string) string { t.Helper() value, ok := values[field] require.Truef(t, ok, "decode gateway session event: missing %s", field) stringValue := gatewayCompatibilityCoerceString(t, value, field) require.NotEmptyf(t, strings.TrimSpace(stringValue), "decode gateway session event: %s must not be empty", field) return stringValue } func gatewayCompatibilityParseInt64Field(t *testing.T, value any, field string) int64 { t.Helper() stringValue := gatewayCompatibilityCoerceString(t, value, field) parsed, err := strconv.ParseInt(strings.TrimSpace(stringValue), 10, 64) require.NoErrorf(t, err, "decode gateway session event: %s", field) return parsed } func gatewayCompatibilityCoerceString(t *testing.T, value any, field string) string { t.Helper() switch typed := value.(type) { case string: return typed case []byte: return string(typed) case fmt.Stringer: return typed.String() case int: return strconv.Itoa(typed) case int64: return strconv.FormatInt(typed, 10) case uint64: return strconv.FormatUint(typed, 10) default: require.Failf(t, "test failed", "decode gateway session event: %s: unsupported value type %T", field, value) return "" } } func gatewayCompatibilityRunServer( t *testing.T, run func(context.Context) error, shutdown func(context.Context) error, addr string, ) { t.Helper() errCh := make(chan error, 1) go func() { errCh <- run(context.Background()) }() gatewayCompatibilityWaitForTCP(t, addr) t.Cleanup(func() { shutdownCtx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() assert.NoError(t, shutdown(shutdownCtx)) assert.NoError(t, <-errCh) }) } func gatewayCompatibilityWaitForTCP(t *testing.T, addr string) { t.Helper() require.Eventually(t, func() bool { conn, err := net.DialTimeout("tcp", addr, 50*time.Millisecond) if err != nil { return false } _ = conn.Close() return true }, 5*time.Second, 25*time.Millisecond) } func gatewayCompatibilityFreeAddr(t *testing.T) string { t.Helper() listener, err := net.Listen("tcp", "127.0.0.1:0") require.NoError(t, err) defer func() { assert.NoError(t, listener.Close()) }() return listener.Addr().String() } type gatewayCompatibilityHTTPResponse struct { StatusCode int Body string } func gatewayCompatibilityPostJSON(t *testing.T, url string, body string) gatewayCompatibilityHTTPResponse { t.Helper() request, err := http.NewRequest(http.MethodPost, url, bytes.NewBufferString(body)) require.NoError(t, err) request.Header.Set("Content-Type", "application/json") response, err := http.DefaultClient.Do(request) require.NoError(t, err) defer response.Body.Close() payload, err := io.ReadAll(response.Body) require.NoError(t, err) return gatewayCompatibilityHTTPResponse{ StatusCode: response.StatusCode, Body: string(payload), } } func gatewayCompatibilityPostJSONValue(t *testing.T, url string, value any) gatewayCompatibilityHTTPResponse { t.Helper() payload, err := json.Marshal(value) require.NoError(t, err) return gatewayCompatibilityPostJSON(t, url, string(payload)) } func gatewayCompatibilityActiveSession( t *testing.T, deviceSessionID string, userID string, clientPublicKeyBase64 string, createdAt time.Time, ) devicesession.Session { t.Helper() keyBytes, err := base64.StdEncoding.DecodeString(clientPublicKeyBase64) require.NoError(t, err) clientPublicKey, err := common.NewClientPublicKey(ed25519.PublicKey(keyBytes)) require.NoError(t, err) session := devicesession.Session{ ID: common.DeviceSessionID(deviceSessionID), UserID: common.UserID(userID), ClientPublicKey: clientPublicKey, Status: devicesession.StatusActive, CreatedAt: createdAt, } require.NoError(t, session.Validate()) return session } func mustGatewayCompatibilityClientPublicKeyBase64() string { key := make([]byte, ed25519.PublicKeySize) for index := range key { key[index] = byte(index + 1) } return base64.StdEncoding.EncodeToString(key) } func int64Pointer(value int64) *int64 { return &value }