summaryrefslogtreecommitdiff
path: root/internal/store/oauth_tokens.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/store/oauth_tokens.go')
-rw-r--r--internal/store/oauth_tokens.go62
1 files changed, 62 insertions, 0 deletions
diff --git a/internal/store/oauth_tokens.go b/internal/store/oauth_tokens.go
new file mode 100644
index 0000000..12df515
--- /dev/null
+++ b/internal/store/oauth_tokens.go
@@ -0,0 +1,62 @@
+package store
+
+import (
+ "database/sql"
+ "time"
+
+ "golang.org/x/oauth2"
+)
+
+// GetOAuthToken returns the stored token for a source (e.g. "google_tasks"),
+// or nil if none has been saved yet (integration not connected).
+func (s *Store) GetOAuthToken(source string) (*oauth2.Token, error) {
+ var accessToken, refreshToken, tokenType string
+ var expiry sql.NullTime
+ err := s.db.QueryRow(`
+ SELECT access_token, refresh_token, token_type, expiry FROM oauth_tokens WHERE source = ?
+ `, source).Scan(&accessToken, &refreshToken, &tokenType, &expiry)
+ if err == sql.ErrNoRows {
+ return nil, nil
+ }
+ if err != nil {
+ return nil, err
+ }
+ tok := &oauth2.Token{
+ AccessToken: accessToken,
+ RefreshToken: refreshToken,
+ TokenType: tokenType,
+ }
+ if expiry.Valid {
+ tok.Expiry = expiry.Time
+ }
+ return tok, nil
+}
+
+// SaveOAuthToken upserts the token for a source. A refreshed token from
+// oauth2.TokenSource sometimes omits RefreshToken (Google only returns it
+// on the initial grant) -- callers must pass the original refresh token
+// through if the new one is empty, since ON CONFLICT here always
+// overwrites rather than preserving the old value.
+func (s *Store) SaveOAuthToken(source string, tok *oauth2.Token) error {
+ var expiry *time.Time
+ if !tok.Expiry.IsZero() {
+ expiry = &tok.Expiry
+ }
+ _, err := s.db.Exec(`
+ INSERT INTO oauth_tokens (source, access_token, refresh_token, token_type, expiry, updated_at)
+ VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
+ ON CONFLICT(source) DO UPDATE SET
+ access_token = excluded.access_token,
+ refresh_token = excluded.refresh_token,
+ token_type = excluded.token_type,
+ expiry = excluded.expiry,
+ updated_at = CURRENT_TIMESTAMP
+ `, source, tok.AccessToken, tok.RefreshToken, tok.TokenType, expiry)
+ return err
+}
+
+// DeleteOAuthToken removes a source's stored token (disconnect).
+func (s *Store) DeleteOAuthToken(source string) error {
+ _, err := s.db.Exec(`DELETE FROM oauth_tokens WHERE source = ?`, source)
+ return err
+}