encrypts integration secrets
This commit is contained in:
@@ -4,9 +4,11 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -16,6 +18,7 @@ import (
|
|||||||
"github.com/pocketbase/pocketbase/forms"
|
"github.com/pocketbase/pocketbase/forms"
|
||||||
"github.com/pocketbase/pocketbase/models"
|
"github.com/pocketbase/pocketbase/models"
|
||||||
"github.com/pocketbase/pocketbase/tools/filesystem"
|
"github.com/pocketbase/pocketbase/tools/filesystem"
|
||||||
|
"github.com/pocketbase/pocketbase/tools/security"
|
||||||
"github.com/twpayne/go-gpx"
|
"github.com/twpayne/go-gpx"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -26,6 +29,11 @@ func SyncKomoot(app *pocketbase.PocketBase) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, i := range integrations {
|
for _, i := range integrations {
|
||||||
|
encryptionKey := os.Getenv("POCKETBASE_ENCRYPTION_KEY")
|
||||||
|
if len(encryptionKey) == 0 {
|
||||||
|
return errors.New("POCKETBASE_ENCRYPTION_KEY not set")
|
||||||
|
}
|
||||||
|
|
||||||
userId := i.GetString("user")
|
userId := i.GetString("user")
|
||||||
komootString := i.GetString("komoot")
|
komootString := i.GetString("komoot")
|
||||||
var komootIntegration KomootIntegration
|
var komootIntegration KomootIntegration
|
||||||
@@ -35,7 +43,13 @@ func SyncKomoot(app *pocketbase.PocketBase) error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
k := &KomootApi{}
|
k := &KomootApi{}
|
||||||
err = k.login(komootIntegration.Email, komootIntegration.Password)
|
|
||||||
|
decryptedPassword, err := security.Decrypt(komootIntegration.Password, encryptionKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = k.Login(komootIntegration.Email, string(decryptedPassword))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
warning := fmt.Sprintf("komoot login failed: %v\n", err)
|
warning := fmt.Sprintf("komoot login failed: %v\n", err)
|
||||||
fmt.Print(warning)
|
fmt.Print(warning)
|
||||||
@@ -114,7 +128,7 @@ func sendRequest(url string, auth *BasicAuthToken) ([]byte, error) {
|
|||||||
return io.ReadAll(resp.Body)
|
return io.ReadAll(resp.Body)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *KomootApi) login(email, password string) error {
|
func (k *KomootApi) Login(email, password string) error {
|
||||||
url := fmt.Sprintf("https://api.komoot.de/v006/account/email/%s/", email)
|
url := fmt.Sprintf("https://api.komoot.de/v006/account/email/%s/", email)
|
||||||
|
|
||||||
body, err := sendRequest(url, &BasicAuthToken{email, password})
|
body, err := sendRequest(url, &BasicAuthToken{email, password})
|
||||||
@@ -222,6 +236,11 @@ func createTrailFromTour(app *pocketbase.PocketBase, detailedTour *DetailedKomoo
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
diffculty := detailedTour.Difficulty.Grade
|
||||||
|
if diffculty == "" {
|
||||||
|
diffculty = "easy"
|
||||||
|
}
|
||||||
|
|
||||||
form.LoadData(map[string]any{
|
form.LoadData(map[string]any{
|
||||||
"name": detailedTour.Name,
|
"name": detailedTour.Name,
|
||||||
"public": detailedTour.Status == "public",
|
"public": detailedTour.Status == "public",
|
||||||
@@ -234,7 +253,7 @@ func createTrailFromTour(app *pocketbase.PocketBase, detailedTour *DetailedKomoo
|
|||||||
"external_id": strconv.Itoa(detailedTour.ID),
|
"external_id": strconv.Itoa(detailedTour.ID),
|
||||||
"lat": detailedTour.StartPoint.Lat,
|
"lat": detailedTour.StartPoint.Lat,
|
||||||
"lon": detailedTour.StartPoint.Lng,
|
"lon": detailedTour.StartPoint.Lng,
|
||||||
"difficulty": detailedTour.Difficulty.Grade,
|
"difficulty": diffculty,
|
||||||
"category": categoryId,
|
"category": categoryId,
|
||||||
"waypoints": wpIds,
|
"waypoints": wpIds,
|
||||||
"author": user,
|
"author": user,
|
||||||
|
|||||||
@@ -2,6 +2,13 @@ package strava
|
|||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
|
type TokenRequest struct {
|
||||||
|
ClientID int32 `json:"client_id"`
|
||||||
|
ClientSecret string `json:"client_secret"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
GrantType string `json:"grant_type"`
|
||||||
|
}
|
||||||
|
|
||||||
type RefreshTokenRequest struct {
|
type RefreshTokenRequest struct {
|
||||||
ClientID int32 `json:"client_id"`
|
ClientID int32 `json:"client_id"`
|
||||||
ClientSecret string `json:"client_secret"`
|
ClientSecret string `json:"client_secret"`
|
||||||
|
|||||||
@@ -3,9 +3,11 @@ package strava
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -14,6 +16,7 @@ import (
|
|||||||
"github.com/pocketbase/pocketbase/forms"
|
"github.com/pocketbase/pocketbase/forms"
|
||||||
"github.com/pocketbase/pocketbase/models"
|
"github.com/pocketbase/pocketbase/models"
|
||||||
"github.com/pocketbase/pocketbase/tools/filesystem"
|
"github.com/pocketbase/pocketbase/tools/filesystem"
|
||||||
|
"github.com/pocketbase/pocketbase/tools/security"
|
||||||
"github.com/twpayne/go-gpx"
|
"github.com/twpayne/go-gpx"
|
||||||
"github.com/twpayne/go-polyline"
|
"github.com/twpayne/go-polyline"
|
||||||
)
|
)
|
||||||
@@ -29,16 +32,35 @@ func SyncStrava(app *pocketbase.PocketBase) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, i := range integrations {
|
for _, i := range integrations {
|
||||||
|
encryptionKey := os.Getenv("POCKETBASE_ENCRYPTION_KEY")
|
||||||
|
if len(encryptionKey) == 0 {
|
||||||
|
return errors.New("POCKETBASE_ENCRYPTION_KEY not set")
|
||||||
|
}
|
||||||
|
|
||||||
userId := i.GetString("user")
|
userId := i.GetString("user")
|
||||||
stravaString := i.GetString("strava")
|
stravaString := i.GetString("strava")
|
||||||
var stravaIntegration StravaIntegration
|
var stravaIntegration StravaIntegration
|
||||||
json.Unmarshal([]byte(stravaString), &stravaIntegration)
|
err := json.Unmarshal([]byte(stravaString), &stravaIntegration)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if !stravaIntegration.Active || stravaIntegration.RefreshToken == "" {
|
if !stravaIntegration.Active || stravaIntegration.RefreshToken == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
r, err := refreshStravaToken(stravaIntegration.ClientID, stravaIntegration.ClientSecret, stravaIntegration.RefreshToken)
|
decryptedSecret, err := security.Decrypt(stravaIntegration.ClientSecret, encryptionKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
request := RefreshTokenRequest{
|
||||||
|
ClientID: stravaIntegration.ClientID,
|
||||||
|
ClientSecret: string(decryptedSecret),
|
||||||
|
RefreshToken: stravaIntegration.RefreshToken,
|
||||||
|
GrantType: "refresh_token",
|
||||||
|
}
|
||||||
|
r, err := GetStravaToken(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
warning := fmt.Sprintf("error refreshing strava access token: %v\n", err)
|
warning := fmt.Sprintf("error refreshing strava access token: %v\n", err)
|
||||||
fmt.Print(warning)
|
fmt.Print(warning)
|
||||||
@@ -60,7 +82,10 @@ func SyncStrava(app *pocketbase.PocketBase) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
i.Set("strava", string(b))
|
i.Set("strava", string(b))
|
||||||
app.Dao().SaveRecord(i)
|
err = app.Dao().SaveRecord(i)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if stravaIntegration.Routes {
|
if stravaIntegration.Routes {
|
||||||
page := 1
|
page := 1
|
||||||
@@ -110,15 +135,10 @@ func SyncStrava(app *pocketbase.PocketBase) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func refreshStravaToken(clientID int32, clientSecret, refreshToken string) (*RefreshTokenResponse, error) {
|
func GetStravaToken(request any) (*RefreshTokenResponse, error) {
|
||||||
const stravaTokenURL = "https://www.strava.com/oauth/token"
|
const stravaTokenURL = "https://www.strava.com/oauth/token"
|
||||||
|
|
||||||
requestBody, err := json.Marshal(RefreshTokenRequest{
|
requestBody, err := json.Marshal(request)
|
||||||
ClientID: clientID,
|
|
||||||
ClientSecret: clientSecret,
|
|
||||||
RefreshToken: refreshToken,
|
|
||||||
GrantType: "refresh_token",
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -137,7 +157,7 @@ func refreshStravaToken(clientID int32, clientSecret, refreshToken string) (*Ref
|
|||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
return nil, fmt.Errorf("failed to refresh token: received status %d", resp.StatusCode)
|
return nil, fmt.Errorf("failed to get token: received status %d", resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
var tokenResponse RefreshTokenResponse
|
var tokenResponse RefreshTokenResponse
|
||||||
|
|||||||
269
db/main.go
269
db/main.go
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"math/rand/v2"
|
"math/rand/v2"
|
||||||
@@ -21,6 +22,7 @@ import (
|
|||||||
"github.com/pocketbase/pocketbase/tools/cron"
|
"github.com/pocketbase/pocketbase/tools/cron"
|
||||||
"github.com/pocketbase/pocketbase/tools/filesystem"
|
"github.com/pocketbase/pocketbase/tools/filesystem"
|
||||||
"github.com/pocketbase/pocketbase/tools/hook"
|
"github.com/pocketbase/pocketbase/tools/hook"
|
||||||
|
"github.com/pocketbase/pocketbase/tools/security"
|
||||||
|
|
||||||
"pocketbase/integrations/komoot"
|
"pocketbase/integrations/komoot"
|
||||||
"pocketbase/integrations/strava"
|
"pocketbase/integrations/strava"
|
||||||
@@ -76,6 +78,12 @@ func setupEventHandlers(app *pocketbase.PocketBase, client meilisearch.ServiceMa
|
|||||||
app.OnRecordAfterCreateRequest("follows").Add(createFollowHandler(app))
|
app.OnRecordAfterCreateRequest("follows").Add(createFollowHandler(app))
|
||||||
app.OnRecordAfterCreateRequest("comments").Add(createCommentHandler(app))
|
app.OnRecordAfterCreateRequest("comments").Add(createCommentHandler(app))
|
||||||
|
|
||||||
|
app.OnRecordsListRequest("integrations").Add(listIntegrationHandler())
|
||||||
|
app.OnRecordAfterCreateRequest("integrations").Add(createIntegrationAfterHandler())
|
||||||
|
app.OnRecordAfterUpdateRequest("integrations").Add(updateIntegrationAfterHandler())
|
||||||
|
app.OnRecordBeforeCreateRequest("integrations").Add(createIntegrationBeforeHandler(app))
|
||||||
|
app.OnRecordBeforeUpdateRequest("integrations").Add(updateIntegrationBeforeHandler(app))
|
||||||
|
|
||||||
app.OnRecordBeforeRequestEmailChangeRequest("users").Add(changeUserEmailHandler(app))
|
app.OnRecordBeforeRequestEmailChangeRequest("users").Add(changeUserEmailHandler(app))
|
||||||
app.OnBeforeServe().Add(onBeforeServeHandler(app, client))
|
app.OnBeforeServe().Add(onBeforeServeHandler(app, client))
|
||||||
}
|
}
|
||||||
@@ -330,6 +338,139 @@ func createCommentHandler(app *pocketbase.PocketBase) func(e *core.RecordCreateE
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func listIntegrationHandler() func(e *core.RecordsListEvent) error {
|
||||||
|
return func(e *core.RecordsListEvent) error {
|
||||||
|
info := apis.RequestInfo(e.HttpContext)
|
||||||
|
if info.Admin != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for _, r := range e.Records {
|
||||||
|
|
||||||
|
err := censorIntegrationSecrets(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createIntegrationBeforeHandler(app *pocketbase.PocketBase) func(e *core.RecordCreateEvent) error {
|
||||||
|
return func(e *core.RecordCreateEvent) error {
|
||||||
|
err := encryptIntegrationSecrets(app, e.Record)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createIntegrationAfterHandler() func(e *core.RecordCreateEvent) error {
|
||||||
|
return func(e *core.RecordCreateEvent) error {
|
||||||
|
err := censorIntegrationSecrets(e.Record)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateIntegrationBeforeHandler(app *pocketbase.PocketBase) func(e *core.RecordUpdateEvent) error {
|
||||||
|
return func(e *core.RecordUpdateEvent) error {
|
||||||
|
err := encryptIntegrationSecrets(app, e.Record)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateIntegrationAfterHandler() func(e *core.RecordUpdateEvent) error {
|
||||||
|
return func(e *core.RecordUpdateEvent) error {
|
||||||
|
err := censorIntegrationSecrets(e.Record)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func censorIntegrationSecrets(r *models.Record) error {
|
||||||
|
secrets := map[string][]string{
|
||||||
|
"strava": {"clientSecret", "refreshToken", "accessToken", "expiresAt"},
|
||||||
|
"komoot": {"password"},
|
||||||
|
}
|
||||||
|
for key, secretKeys := range secrets {
|
||||||
|
if integrationString := r.GetString(key); integrationString != "" {
|
||||||
|
var integration map[string]interface{}
|
||||||
|
if err := json.Unmarshal([]byte(integrationString), &integration); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, secretKey := range secretKeys {
|
||||||
|
integration[secretKey] = ""
|
||||||
|
}
|
||||||
|
b, err := json.Marshal(integration)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.Set(key, string(b))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func encryptIntegrationSecrets(app *pocketbase.PocketBase, r *models.Record) error {
|
||||||
|
encryptionKey := os.Getenv("POCKETBASE_ENCRYPTION_KEY")
|
||||||
|
if len(encryptionKey) == 0 {
|
||||||
|
return apis.NewBadRequestError("POCKETBASE_ENCRYPTION_KEY not set", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
secrets := map[string][]string{
|
||||||
|
"strava": {"clientSecret", "refreshToken", "accessToken", "expiresAt"},
|
||||||
|
"komoot": {"password"},
|
||||||
|
}
|
||||||
|
|
||||||
|
original, _ := app.Dao().FindRecordById("integrations", r.Id)
|
||||||
|
|
||||||
|
for key, secretKeys := range secrets {
|
||||||
|
if integrationString := r.GetString(key); integrationString != "" {
|
||||||
|
var integration map[string]interface{}
|
||||||
|
if err := json.Unmarshal([]byte(integrationString), &integration); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, secretKey := range secretKeys {
|
||||||
|
if secret, ok := integration[secretKey].(string); ok && len(secret) > 0 {
|
||||||
|
encryptedSecret, err := security.Encrypt([]byte(secret), encryptionKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
integration[secretKey] = encryptedSecret
|
||||||
|
} else if original != nil {
|
||||||
|
|
||||||
|
originalString := original.GetString(key)
|
||||||
|
var originalIntegration map[string]interface{}
|
||||||
|
if err := json.Unmarshal([]byte(originalString), &originalIntegration); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
integration[secretKey] = originalIntegration[secretKey]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := json.Marshal(integration)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.Set(key, string(b))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func changeUserEmailHandler(app *pocketbase.PocketBase) func(e *core.RecordRequestEmailChangeEvent) error {
|
func changeUserEmailHandler(app *pocketbase.PocketBase) func(e *core.RecordRequestEmailChangeEvent) error {
|
||||||
return func(e *core.RecordRequestEmailChangeEvent) error {
|
return func(e *core.RecordRequestEmailChangeEvent) error {
|
||||||
form := forms.NewRecordEmailChangeRequest(app, e.Record)
|
form := forms.NewRecordEmailChangeRequest(app, e.Record)
|
||||||
@@ -402,22 +543,144 @@ func registerRoutes(e *core.ServeEvent, app *pocketbase.PocketBase, client meili
|
|||||||
return c.JSON(http.StatusOK, randomTrails)
|
return c.JSON(http.StatusOK, randomTrails)
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
e.Router.POST("/integration/strava/token", func(c echo.Context) error {
|
||||||
|
encryptionKey := os.Getenv("POCKETBASE_ENCRYPTION_KEY")
|
||||||
|
if len(encryptionKey) == 0 {
|
||||||
|
return apis.NewBadRequestError("POCKETBASE_ENCRYPTION_KEY not set", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
var data strava.TokenRequest
|
||||||
|
if err := c.Bind(&data); err != nil {
|
||||||
|
return apis.NewBadRequestError("Failed to read request data", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
user, success := c.Get(apis.ContextAuthRecordKey).(*models.Record)
|
||||||
|
userId := ""
|
||||||
|
if success {
|
||||||
|
userId = user.Id
|
||||||
|
}
|
||||||
|
|
||||||
|
integrations, err := app.Dao().FindRecordsByExpr("integrations", dbx.NewExp("user = {:id}", dbx.Params{"id": userId}))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(integrations) == 0 {
|
||||||
|
return apis.NewBadRequestError("user has no integration", nil)
|
||||||
|
}
|
||||||
|
integration := integrations[0]
|
||||||
|
stravaString := integration.GetString("strava")
|
||||||
|
if len(stravaString) == 0 {
|
||||||
|
return apis.NewBadRequestError("strava integration missing", nil)
|
||||||
|
}
|
||||||
|
var stravaIntegration strava.StravaIntegration
|
||||||
|
err = json.Unmarshal([]byte(stravaString), &stravaIntegration)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
decryptedSecret, err := security.Decrypt(stravaIntegration.ClientSecret, encryptionKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
request := strava.TokenRequest{
|
||||||
|
ClientID: stravaIntegration.ClientID,
|
||||||
|
ClientSecret: string(decryptedSecret),
|
||||||
|
Code: data.Code,
|
||||||
|
GrantType: "authorization_code",
|
||||||
|
}
|
||||||
|
r, err := strava.GetStravaToken(request)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if r.AccessToken != "" {
|
||||||
|
stravaIntegration.AccessToken = r.AccessToken
|
||||||
|
}
|
||||||
|
if r.RefreshToken != "" {
|
||||||
|
stravaIntegration.RefreshToken = r.RefreshToken
|
||||||
|
}
|
||||||
|
if r.AccessToken != "" {
|
||||||
|
stravaIntegration.ExpiresAt = r.ExpiresAt
|
||||||
|
}
|
||||||
|
|
||||||
|
stravaIntegration.Active = true
|
||||||
|
|
||||||
|
b, err := json.Marshal(stravaIntegration)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
integration.Set("strava", string(b))
|
||||||
|
err = app.Dao().SaveRecord(integration)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return c.JSON(http.StatusOK, nil)
|
||||||
|
})
|
||||||
|
|
||||||
|
e.Router.GET("/integration/komoot/login", func(c echo.Context) error {
|
||||||
|
encryptionKey := os.Getenv("POCKETBASE_ENCRYPTION_KEY")
|
||||||
|
if len(encryptionKey) == 0 {
|
||||||
|
return apis.NewBadRequestError("POCKETBASE_ENCRYPTION_KEY not set", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
user, success := c.Get(apis.ContextAuthRecordKey).(*models.Record)
|
||||||
|
userId := ""
|
||||||
|
if success {
|
||||||
|
userId = user.Id
|
||||||
|
}
|
||||||
|
|
||||||
|
integrations, err := app.Dao().FindRecordsByExpr("integrations", dbx.NewExp("user = {:id}", dbx.Params{"id": userId}))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(integrations) == 0 {
|
||||||
|
return apis.NewBadRequestError("user has no integration", nil)
|
||||||
|
}
|
||||||
|
integration := integrations[0]
|
||||||
|
komootString := integration.GetString("komoot")
|
||||||
|
if len(komootString) == 0 {
|
||||||
|
return apis.NewBadRequestError("komoot integration missing", nil)
|
||||||
|
}
|
||||||
|
var komootIntegration komoot.KomootIntegration
|
||||||
|
err = json.Unmarshal([]byte(komootString), &komootIntegration)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
decryptedPassword, err := security.Decrypt(komootIntegration.Password, encryptionKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
k := &komoot.KomootApi{}
|
||||||
|
|
||||||
|
err = k.Login(komootIntegration.Email, string(decryptedPassword))
|
||||||
|
if err != nil {
|
||||||
|
return apis.NewUnauthorizedError("invalid credentials", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, nil)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func registerCronJobs(app *pocketbase.PocketBase) {
|
func registerCronJobs(app *pocketbase.PocketBase) {
|
||||||
scheduler := cron.New()
|
scheduler := cron.New()
|
||||||
|
|
||||||
scheduler.MustAdd("integrations", "*/5 * * * *", func() {
|
schedule := os.Getenv("POCKETBASE_CRON_SYNC_SCHEDULE")
|
||||||
|
if len(schedule) == 0 {
|
||||||
|
schedule = "0 2 * * *"
|
||||||
|
}
|
||||||
|
|
||||||
|
scheduler.MustAdd("integrations", schedule, func() {
|
||||||
err := strava.SyncStrava(app)
|
err := strava.SyncStrava(app)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
warning := fmt.Sprintf("Error syncing with strava: %v", err)
|
warning := fmt.Sprintf("Error syncing with strava: %v", err)
|
||||||
fmt.Print(warning)
|
fmt.Println(warning)
|
||||||
app.Logger().Error(warning)
|
app.Logger().Error(warning)
|
||||||
}
|
}
|
||||||
err = komoot.SyncKomoot(app)
|
err = komoot.SyncKomoot(app)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
warning := fmt.Sprintf("Error syncing with komoot: %v", err)
|
warning := fmt.Sprintf("Error syncing with komoot: %v", err)
|
||||||
fmt.Print(warning)
|
fmt.Println(warning)
|
||||||
app.Logger().Error(warning)
|
app.Logger().Error(warning)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -60,6 +60,7 @@
|
|||||||
></TextField>
|
></TextField>
|
||||||
<TextField
|
<TextField
|
||||||
label={$_("password")}
|
label={$_("password")}
|
||||||
|
placeholder={integration?.komoot ? `(${$_("unchanged")})` : ""}
|
||||||
name="password"
|
name="password"
|
||||||
type="password"
|
type="password"
|
||||||
error={$errors.password}
|
error={$errors.password}
|
||||||
|
|||||||
@@ -21,14 +21,11 @@
|
|||||||
let modal: Modal;
|
let modal: Modal;
|
||||||
|
|
||||||
export function openModal() {
|
export function openModal() {
|
||||||
errors.set({})
|
errors.set({});
|
||||||
modal.openModal();
|
modal.openModal();
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const { form, errors } = createForm({
|
||||||
form,
|
|
||||||
errors,
|
|
||||||
} = createForm({
|
|
||||||
initialValues: {
|
initialValues: {
|
||||||
clientId: integration?.strava?.clientId ?? "",
|
clientId: integration?.strava?.clientId ?? "",
|
||||||
clientSecret: integration?.strava?.clientSecret ?? "",
|
clientSecret: integration?.strava?.clientSecret ?? "",
|
||||||
@@ -62,7 +59,7 @@
|
|||||||
></TextField>
|
></TextField>
|
||||||
<TextField
|
<TextField
|
||||||
label="Client Secret"
|
label="Client Secret"
|
||||||
placeholder="de8b3789bd7116d..."
|
placeholder={integration?.strava ? `(${$_("unchanged")})` : "de8b3789bd7116d..."}
|
||||||
name="clientSecret"
|
name="clientSecret"
|
||||||
type="password"
|
type="password"
|
||||||
error={$errors.clientSecret}
|
error={$errors.clientSecret}
|
||||||
|
|||||||
@@ -147,7 +147,9 @@
|
|||||||
"include-description": "Beschreibung übernehmen",
|
"include-description": "Beschreibung übernehmen",
|
||||||
"integration-description-komoot": "",
|
"integration-description-komoot": "",
|
||||||
"integration-description-strava": "",
|
"integration-description-strava": "",
|
||||||
|
"integration-disabled": "",
|
||||||
"integrations": "",
|
"integrations": "",
|
||||||
|
"integration-enabled": "",
|
||||||
"invalid-date": "Ungültiges Datum",
|
"invalid-date": "Ungültiges Datum",
|
||||||
"invalid-username": "Ungültiger Nutzername",
|
"invalid-username": "Ungültiger Nutzername",
|
||||||
"italian": "Italienisch",
|
"italian": "Italienisch",
|
||||||
@@ -289,6 +291,7 @@
|
|||||||
"trail-not-shared": "Route mit niemandem geteilt",
|
"trail-not-shared": "Route mit niemandem geteilt",
|
||||||
"trail-saved-successfully": "Route gespeichert",
|
"trail-saved-successfully": "Route gespeichert",
|
||||||
"trails-for-you": "Routen für dich",
|
"trails-for-you": "Routen für dich",
|
||||||
|
"unchanged": "",
|
||||||
"units": "Einheiten",
|
"units": "Einheiten",
|
||||||
"upload-file": "Datei hochladen",
|
"upload-file": "Datei hochladen",
|
||||||
"upload-gpx": "GPX hochladen",
|
"upload-gpx": "GPX hochladen",
|
||||||
|
|||||||
@@ -147,7 +147,9 @@
|
|||||||
"include-description": "Include description",
|
"include-description": "Include description",
|
||||||
"integration-description-komoot": "Syncs your komoot tours with wanderer in regular intervals.",
|
"integration-description-komoot": "Syncs your komoot tours with wanderer in regular intervals.",
|
||||||
"integration-description-strava": "Syncs your strava routes & activities with wanderer in regular intervals.",
|
"integration-description-strava": "Syncs your strava routes & activities with wanderer in regular intervals.",
|
||||||
|
"integration-disabled": "integration disabled",
|
||||||
"integrations": "Integrations",
|
"integrations": "Integrations",
|
||||||
|
"integration-enabled": "integration enabled",
|
||||||
"invalid-date": "Invalid Date",
|
"invalid-date": "Invalid Date",
|
||||||
"invalid-username": "Invalid username",
|
"invalid-username": "Invalid username",
|
||||||
"italian": "Italian",
|
"italian": "Italian",
|
||||||
@@ -289,6 +291,7 @@
|
|||||||
"trail-not-shared": "Not shared with anyone",
|
"trail-not-shared": "Not shared with anyone",
|
||||||
"trail-saved-successfully": "Trail saved successfully",
|
"trail-saved-successfully": "Trail saved successfully",
|
||||||
"trails-for-you": "Trails for you",
|
"trails-for-you": "Trails for you",
|
||||||
|
"unchanged": "unchanged",
|
||||||
"units": "Units",
|
"units": "Units",
|
||||||
"upload-file": "Upload file",
|
"upload-file": "Upload file",
|
||||||
"upload-gpx": "Upload GPX",
|
"upload-gpx": "Upload GPX",
|
||||||
|
|||||||
@@ -147,7 +147,9 @@
|
|||||||
"include-description": "Incluir descripción",
|
"include-description": "Incluir descripción",
|
||||||
"integration-description-komoot": "",
|
"integration-description-komoot": "",
|
||||||
"integration-description-strava": "",
|
"integration-description-strava": "",
|
||||||
|
"integration-disabled": "",
|
||||||
"integrations": "",
|
"integrations": "",
|
||||||
|
"integration-enabled": "",
|
||||||
"invalid-date": "Fecha no válida",
|
"invalid-date": "Fecha no válida",
|
||||||
"invalid-username": "Usuario no válido",
|
"invalid-username": "Usuario no válido",
|
||||||
"italian": "Italiano",
|
"italian": "Italiano",
|
||||||
@@ -289,6 +291,7 @@
|
|||||||
"trail-not-shared": "No compartida con nadie",
|
"trail-not-shared": "No compartida con nadie",
|
||||||
"trail-saved-successfully": "Ruta guardada con éxito",
|
"trail-saved-successfully": "Ruta guardada con éxito",
|
||||||
"trails-for-you": "Rutas para ti",
|
"trails-for-you": "Rutas para ti",
|
||||||
|
"unchanged": "",
|
||||||
"units": "Unidades",
|
"units": "Unidades",
|
||||||
"upload-file": "Cargar archivo",
|
"upload-file": "Cargar archivo",
|
||||||
"upload-gpx": "Cargar GPX",
|
"upload-gpx": "Cargar GPX",
|
||||||
|
|||||||
@@ -147,7 +147,9 @@
|
|||||||
"include-description": "Inclure la description",
|
"include-description": "Inclure la description",
|
||||||
"integration-description-komoot": "",
|
"integration-description-komoot": "",
|
||||||
"integration-description-strava": "",
|
"integration-description-strava": "",
|
||||||
|
"integration-disabled": "",
|
||||||
"integrations": "",
|
"integrations": "",
|
||||||
|
"integration-enabled": "",
|
||||||
"invalid-date": "Date invalide",
|
"invalid-date": "Date invalide",
|
||||||
"invalid-username": "Nom d'utilisateur invalide",
|
"invalid-username": "Nom d'utilisateur invalide",
|
||||||
"italian": "Italien",
|
"italian": "Italien",
|
||||||
@@ -289,6 +291,7 @@
|
|||||||
"trail-not-shared": "L'itinéraire n'a pas été partagé",
|
"trail-not-shared": "L'itinéraire n'a pas été partagé",
|
||||||
"trail-saved-successfully": "Itinéraire enregistrée",
|
"trail-saved-successfully": "Itinéraire enregistrée",
|
||||||
"trails-for-you": "Itinéraire pour vous",
|
"trails-for-you": "Itinéraire pour vous",
|
||||||
|
"unchanged": "",
|
||||||
"units": "Unités",
|
"units": "Unités",
|
||||||
"upload-file": "Importer un fichier",
|
"upload-file": "Importer un fichier",
|
||||||
"upload-gpx": "Envoyer un GPX",
|
"upload-gpx": "Envoyer un GPX",
|
||||||
|
|||||||
@@ -147,7 +147,9 @@
|
|||||||
"include-description": "Include description",
|
"include-description": "Include description",
|
||||||
"integration-description-komoot": "",
|
"integration-description-komoot": "",
|
||||||
"integration-description-strava": "",
|
"integration-description-strava": "",
|
||||||
|
"integration-disabled": "",
|
||||||
"integrations": "",
|
"integrations": "",
|
||||||
|
"integration-enabled": "",
|
||||||
"invalid-date": "Érvénytelen dátum",
|
"invalid-date": "Érvénytelen dátum",
|
||||||
"invalid-username": "Érvénytelen felhasználó",
|
"invalid-username": "Érvénytelen felhasználó",
|
||||||
"italian": "Olasz",
|
"italian": "Olasz",
|
||||||
@@ -289,6 +291,7 @@
|
|||||||
"trail-not-shared": "Not shared with anyone",
|
"trail-not-shared": "Not shared with anyone",
|
||||||
"trail-saved-successfully": "Trail saved successfully",
|
"trail-saved-successfully": "Trail saved successfully",
|
||||||
"trails-for-you": "Útvonalak önnek",
|
"trails-for-you": "Útvonalak önnek",
|
||||||
|
"unchanged": "",
|
||||||
"units": "Mértékegységek",
|
"units": "Mértékegységek",
|
||||||
"upload-file": "Fájl feltöltése",
|
"upload-file": "Fájl feltöltése",
|
||||||
"upload-gpx": "GPX feltöltése",
|
"upload-gpx": "GPX feltöltése",
|
||||||
|
|||||||
@@ -147,7 +147,9 @@
|
|||||||
"include-description": "Adotta descrizione",
|
"include-description": "Adotta descrizione",
|
||||||
"integration-description-komoot": "",
|
"integration-description-komoot": "",
|
||||||
"integration-description-strava": "",
|
"integration-description-strava": "",
|
||||||
|
"integration-disabled": "",
|
||||||
"integrations": "",
|
"integrations": "",
|
||||||
|
"integration-enabled": "",
|
||||||
"invalid-date": "Data non valida",
|
"invalid-date": "Data non valida",
|
||||||
"invalid-username": "Nome utente non valido",
|
"invalid-username": "Nome utente non valido",
|
||||||
"italian": "Italiano",
|
"italian": "Italiano",
|
||||||
@@ -289,6 +291,7 @@
|
|||||||
"trail-not-shared": "Percorso non condiviso con nessuno",
|
"trail-not-shared": "Percorso non condiviso con nessuno",
|
||||||
"trail-saved-successfully": "Percorso salvato con successo",
|
"trail-saved-successfully": "Percorso salvato con successo",
|
||||||
"trails-for-you": "Percorsi per te",
|
"trails-for-you": "Percorsi per te",
|
||||||
|
"unchanged": "",
|
||||||
"units": "Unità",
|
"units": "Unità",
|
||||||
"upload-file": "Carica file",
|
"upload-file": "Carica file",
|
||||||
"upload-gpx": "Carica file GPX",
|
"upload-gpx": "Carica file GPX",
|
||||||
|
|||||||
@@ -147,7 +147,9 @@
|
|||||||
"include-description": "Inclusief beschrijving",
|
"include-description": "Inclusief beschrijving",
|
||||||
"integration-description-komoot": "",
|
"integration-description-komoot": "",
|
||||||
"integration-description-strava": "",
|
"integration-description-strava": "",
|
||||||
|
"integration-disabled": "",
|
||||||
"integrations": "",
|
"integrations": "",
|
||||||
|
"integration-enabled": "",
|
||||||
"invalid-date": "Ongeldige datum",
|
"invalid-date": "Ongeldige datum",
|
||||||
"invalid-username": "Ongeldige gebruikersnaam",
|
"invalid-username": "Ongeldige gebruikersnaam",
|
||||||
"italian": "Italiaans",
|
"italian": "Italiaans",
|
||||||
@@ -289,6 +291,7 @@
|
|||||||
"trail-not-shared": "Not shared with anyone",
|
"trail-not-shared": "Not shared with anyone",
|
||||||
"trail-saved-successfully": "Trail saved successfully",
|
"trail-saved-successfully": "Trail saved successfully",
|
||||||
"trails-for-you": "Wandelroutes voor jou",
|
"trails-for-you": "Wandelroutes voor jou",
|
||||||
|
"unchanged": "",
|
||||||
"units": "Eenheden",
|
"units": "Eenheden",
|
||||||
"upload-file": "Bestand uploaden",
|
"upload-file": "Bestand uploaden",
|
||||||
"upload-gpx": "GPX-bestand uploaden",
|
"upload-gpx": "GPX-bestand uploaden",
|
||||||
|
|||||||
@@ -147,7 +147,9 @@
|
|||||||
"include-description": "Dołącz opis",
|
"include-description": "Dołącz opis",
|
||||||
"integration-description-komoot": "",
|
"integration-description-komoot": "",
|
||||||
"integration-description-strava": "",
|
"integration-description-strava": "",
|
||||||
|
"integration-disabled": "",
|
||||||
"integrations": "",
|
"integrations": "",
|
||||||
|
"integration-enabled": "",
|
||||||
"invalid-date": "Nieprawidłowa data",
|
"invalid-date": "Nieprawidłowa data",
|
||||||
"invalid-username": "Błędna nazwa użytkownika",
|
"invalid-username": "Błędna nazwa użytkownika",
|
||||||
"italian": "Włoski",
|
"italian": "Włoski",
|
||||||
@@ -289,6 +291,7 @@
|
|||||||
"trail-not-shared": "Szlak nie udostępniony",
|
"trail-not-shared": "Szlak nie udostępniony",
|
||||||
"trail-saved-successfully": "Szlak pomyślnie zapisany",
|
"trail-saved-successfully": "Szlak pomyślnie zapisany",
|
||||||
"trails-for-you": "Szlaki dla ciebie",
|
"trails-for-you": "Szlaki dla ciebie",
|
||||||
|
"unchanged": "",
|
||||||
"units": "Jednostki",
|
"units": "Jednostki",
|
||||||
"upload-file": "Przesyłanie pliku",
|
"upload-file": "Przesyłanie pliku",
|
||||||
"upload-gpx": "Importuj GPX",
|
"upload-gpx": "Importuj GPX",
|
||||||
|
|||||||
@@ -147,7 +147,9 @@
|
|||||||
"include-description": "Incluir descrição",
|
"include-description": "Incluir descrição",
|
||||||
"integration-description-komoot": "",
|
"integration-description-komoot": "",
|
||||||
"integration-description-strava": "",
|
"integration-description-strava": "",
|
||||||
|
"integration-disabled": "",
|
||||||
"integrations": "",
|
"integrations": "",
|
||||||
|
"integration-enabled": "",
|
||||||
"invalid-date": "Data inválida",
|
"invalid-date": "Data inválida",
|
||||||
"invalid-username": "Nome de usuário inválido",
|
"invalid-username": "Nome de usuário inválido",
|
||||||
"italian": "Italiano",
|
"italian": "Italiano",
|
||||||
@@ -289,6 +291,7 @@
|
|||||||
"trail-not-shared": "Não partilhado com ninguém",
|
"trail-not-shared": "Não partilhado com ninguém",
|
||||||
"trail-saved-successfully": "Percurso gravado com sucesso",
|
"trail-saved-successfully": "Percurso gravado com sucesso",
|
||||||
"trails-for-you": "Trilhos para si",
|
"trails-for-you": "Trilhos para si",
|
||||||
|
"unchanged": "",
|
||||||
"units": "Unidades",
|
"units": "Unidades",
|
||||||
"upload-file": "Subir arquivo",
|
"upload-file": "Subir arquivo",
|
||||||
"upload-gpx": "Carregar GPX",
|
"upload-gpx": "Carregar GPX",
|
||||||
|
|||||||
@@ -147,7 +147,9 @@
|
|||||||
"include-description": "包含描述",
|
"include-description": "包含描述",
|
||||||
"integration-description-komoot": "",
|
"integration-description-komoot": "",
|
||||||
"integration-description-strava": "",
|
"integration-description-strava": "",
|
||||||
|
"integration-disabled": "",
|
||||||
"integrations": "",
|
"integrations": "",
|
||||||
|
"integration-enabled": "",
|
||||||
"invalid-date": "无效日期",
|
"invalid-date": "无效日期",
|
||||||
"invalid-username": "无效用户名",
|
"invalid-username": "无效用户名",
|
||||||
"italian": "意大利语",
|
"italian": "意大利语",
|
||||||
@@ -289,6 +291,7 @@
|
|||||||
"trail-not-shared": "未与任何人分享",
|
"trail-not-shared": "未与任何人分享",
|
||||||
"trail-saved-successfully": "路线保存成功",
|
"trail-saved-successfully": "路线保存成功",
|
||||||
"trails-for-you": "推荐路线",
|
"trails-for-you": "推荐路线",
|
||||||
|
"unchanged": "",
|
||||||
"units": "单位",
|
"units": "单位",
|
||||||
"upload-file": "上传文件",
|
"upload-file": "上传文件",
|
||||||
"upload-gpx": "上传 GPX",
|
"upload-gpx": "上传 GPX",
|
||||||
|
|||||||
@@ -3,12 +3,9 @@ import type { Integration } from "../integration";
|
|||||||
|
|
||||||
const StravaSchema = z.object({
|
const StravaSchema = z.object({
|
||||||
clientId: z.number({ coerce: true }).int().positive(),
|
clientId: z.number({ coerce: true }).int().positive(),
|
||||||
clientSecret: z.string().length(40),
|
clientSecret: z.string().length(40).optional().or(z.literal('')),
|
||||||
routes: z.boolean(),
|
routes: z.boolean(),
|
||||||
activities: z.boolean(),
|
activities: z.boolean(),
|
||||||
accessToken: z.string().length(40).optional(),
|
|
||||||
refreshToken: z.string().length(40).optional(),
|
|
||||||
expiresAt: z.number().int().positive().optional(),
|
|
||||||
active: z.boolean()
|
active: z.boolean()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ export interface BaseIntegration {
|
|||||||
|
|
||||||
export interface StravaIntegration extends BaseIntegration {
|
export interface StravaIntegration extends BaseIntegration {
|
||||||
clientId: string | number;
|
clientId: string | number;
|
||||||
clientSecret: string;
|
clientSecret?: string;
|
||||||
routes: boolean;
|
routes: boolean;
|
||||||
activities: boolean;
|
activities: boolean;
|
||||||
accessToken?: string;
|
accessToken?: string;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
type KomootIntegration,
|
type KomootIntegration,
|
||||||
type StravaIntegration,
|
type StravaIntegration,
|
||||||
} from "$lib/models/integration.js";
|
} from "$lib/models/integration.js";
|
||||||
|
import { pb } from "$lib/pocketbase.js";
|
||||||
import {
|
import {
|
||||||
integrations_create,
|
integrations_create,
|
||||||
integrations_update,
|
integrations_update,
|
||||||
@@ -29,7 +30,9 @@
|
|||||||
);
|
);
|
||||||
|
|
||||||
let komootSettingsModal: KomootSettingsModal;
|
let komootSettingsModal: KomootSettingsModal;
|
||||||
let komootToggleValue: boolean = $state(data.integration?.komoot?.active ?? false);
|
let komootToggleValue: boolean = $state(
|
||||||
|
data.integration?.komoot?.active ?? false,
|
||||||
|
);
|
||||||
|
|
||||||
async function onSettingsSave(
|
async function onSettingsSave(
|
||||||
form: StravaIntegration | KomootIntegration,
|
form: StravaIntegration | KomootIntegration,
|
||||||
@@ -97,6 +100,14 @@
|
|||||||
type: "error",
|
type: "error",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
show_toast({
|
||||||
|
text:
|
||||||
|
"strava " +
|
||||||
|
$_("integration-disabled"),
|
||||||
|
icon: "check",
|
||||||
|
type: "success",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,14 +116,11 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (value) {
|
if (value) {
|
||||||
const authUrl = `https://api.komoot.de/v006/account/email/${integration.komoot.email}/`;
|
try {
|
||||||
const r = await fetch(authUrl, {
|
await pb.send("/integration/komoot/login", {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
});
|
||||||
Authorization: `Basic ${btoa(integration.komoot.email + ":" + integration.komoot.password)}`,
|
} catch (e) {
|
||||||
},
|
|
||||||
});
|
|
||||||
if (!r.ok) {
|
|
||||||
komootToggleValue = false;
|
komootToggleValue = false;
|
||||||
show_toast({
|
show_toast({
|
||||||
text: $_("error-logging-in-to-komoot"),
|
text: $_("error-logging-in-to-komoot"),
|
||||||
@@ -133,6 +141,13 @@
|
|||||||
type: "error",
|
type: "error",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
show_toast({
|
||||||
|
text:
|
||||||
|
"komoot " + $_(`integration-${value ? "enabled" : "disabled"}`),
|
||||||
|
icon: "check",
|
||||||
|
type: "success",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
|
import { pb } from "$lib/pocketbase";
|
||||||
import { integrations_index, integrations_update } from "$lib/stores/integration_store";
|
import { integrations_index, integrations_update } from "$lib/stores/integration_store";
|
||||||
import { error, redirect, type RequestEvent, type ServerLoad } from "@sveltejs/kit";
|
import { error, redirect, type RequestEvent, type ServerLoad } from "@sveltejs/kit";
|
||||||
|
import { ClientResponseError } from "pocketbase";
|
||||||
|
|
||||||
export const load: ServerLoad = async ({ url, fetch }) => {
|
export const load: ServerLoad = async ({ url, fetch }) => {
|
||||||
const oauthError = url.searchParams.get('error');
|
const oauthError = url.searchParams.get('error');
|
||||||
if (oauthError) {
|
if (oauthError) {
|
||||||
// user cancelled
|
// user cancelled
|
||||||
if(oauthError == "access_denied") {
|
if (oauthError == "access_denied") {
|
||||||
return redirect(302, '/settings/integrations')
|
return redirect(302, '/settings/integrations')
|
||||||
}
|
}
|
||||||
return error(400, {
|
return error(400, {
|
||||||
@@ -19,41 +21,23 @@ export const load: ServerLoad = async ({ url, fetch }) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const integrations = await integrations_index(fetch);
|
try {
|
||||||
|
await pb.send("/integration/strava/token", {
|
||||||
if (!integrations.length || !integrations[0].strava) {
|
method: 'POST',
|
||||||
return error(400, {
|
headers: { 'Content-Type': 'application/json' },
|
||||||
message: "Missing integration record"
|
body: JSON.stringify({
|
||||||
|
code,
|
||||||
|
grant_type: 'authorization_code'
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
|
||||||
|
if (e instanceof ClientResponseError) {
|
||||||
|
return error(e.status, e.message);
|
||||||
|
|
||||||
|
}
|
||||||
|
throw e
|
||||||
}
|
}
|
||||||
|
|
||||||
const integration = integrations[0]
|
|
||||||
|
|
||||||
const tokenResponse = await fetch('https://www.strava.com/oauth/token', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({
|
|
||||||
client_id: integration.strava?.clientId,
|
|
||||||
client_secret: integration.strava?.clientSecret,
|
|
||||||
code,
|
|
||||||
grant_type: 'authorization_code'
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!tokenResponse.ok) {
|
|
||||||
const r = await tokenResponse.json()
|
|
||||||
console.error(r)
|
|
||||||
return error(500, 'Failed to get access token');
|
|
||||||
}
|
|
||||||
|
|
||||||
const { access_token, refresh_token, expires_at } = await tokenResponse.json();
|
|
||||||
|
|
||||||
integration.strava!.accessToken = access_token
|
|
||||||
integration.strava!.refreshToken = refresh_token
|
|
||||||
integration.strava!.expiresAt = expires_at
|
|
||||||
integration.strava!.active = true
|
|
||||||
|
|
||||||
await integrations_update(integration, fetch);
|
|
||||||
|
|
||||||
return redirect(302, '/settings/integrations')
|
return redirect(302, '/settings/integrations')
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user