Skip to content

Commit

Permalink
[pocketbase#2533] added VK OAuth2 provider
Browse files Browse the repository at this point in the history
Co-authored-by: Valentine <[email protected]>
  • Loading branch information
ganigeorgiev and imperatrona committed May 24, 2023
1 parent e40cf46 commit af71b63
Show file tree
Hide file tree
Showing 41 changed files with 173 additions and 39 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
## v0.17.0-WIP

- Added Instagram OAuth2 ([#2534](https://github.com/pocketbase/pocketbase/pull/2534); thanks @pnmcosta).
- Added Instagram OAuth2 provider ([#2534](https://github.com/pocketbase/pocketbase/pull/2534); thanks @pnmcosta).

- Added VK OAuth2 provider ([#2533](https://github.com/pocketbase/pocketbase/pull/2533); thanks @imperatrona).


## v0.16.2
Expand Down
7 changes: 6 additions & 1 deletion apis/record_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,14 @@ func (api *recordAuthApi) authMethods(c echo.Context) error {
oauth2.SetAuthURLParam("code_challenge_method", codeChallengeMethod),
}

if name == auth.NameApple {
// custom providers url options
switch name {
case auth.NameApple:
// see https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_js/incorporating_sign_in_with_apple_into_other_platforms#3332113
urlOpts = append(urlOpts, oauth2.SetAuthURLParam("response_mode", "query"))
case auth.NameVK:
// vk currently doesn't support PKCE for server-side authorization
urlOpts = []oauth2.AuthCodeOption{}
}

result.AuthProviders = append(result.AuthProviders, providerInfo{
Expand Down
3 changes: 3 additions & 0 deletions apis/settings_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ func TestSettingsList(t *testing.T) {
`"oidc3Auth":{`,
`"appleAuth":{`,
`"instagramAuth":{`,
`"vkAuth":{`,
`"secret":"******"`,
`"clientSecret":"******"`,
},
Expand Down Expand Up @@ -155,6 +156,7 @@ func TestSettingsSet(t *testing.T) {
`"oidc3Auth":{`,
`"appleAuth":{`,
`"instagramAuth":{`,
`"vkAuth":{`,
`"secret":"******"`,
`"clientSecret":"******"`,
`"appName":"acme_test"`,
Expand Down Expand Up @@ -223,6 +225,7 @@ func TestSettingsSet(t *testing.T) {
`"oidc3Auth":{`,
`"appleAuth":{`,
`"instagramAuth":{`,
`"vkAuth":{`,
`"secret":"******"`,
`"clientSecret":"******"`,
`"appName":"update_test"`,
Expand Down
7 changes: 7 additions & 0 deletions models/settings/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ type Settings struct {
OIDC3Auth AuthProviderConfig `form:"oidc3Auth" json:"oidc3Auth"`
AppleAuth AuthProviderConfig `form:"appleAuth" json:"appleAuth"`
InstagramAuth AuthProviderConfig `form:"instagramAuth" json:"instagramAuth"`
VKAuth AuthProviderConfig `form:"vkAuth" json:"vkAuth"`
}

// New creates and returns a new default Settings instance.
Expand Down Expand Up @@ -179,6 +180,9 @@ func New() *Settings {
InstagramAuth: AuthProviderConfig{
Enabled: false,
},
VKAuth: AuthProviderConfig{
Enabled: false,
},
}
}

Expand Down Expand Up @@ -220,6 +224,7 @@ func (s *Settings) Validate() error {
validation.Field(&s.OIDC3Auth),
validation.Field(&s.AppleAuth),
validation.Field(&s.InstagramAuth),
validation.Field(&s.VKAuth),
)
}

Expand Down Expand Up @@ -284,6 +289,7 @@ func (s *Settings) RedactClone() (*Settings, error) {
&clone.OIDC3Auth.ClientSecret,
&clone.AppleAuth.ClientSecret,
&clone.InstagramAuth.ClientSecret,
&clone.VKAuth.ClientSecret,
}

// mask all sensitive fields
Expand Down Expand Up @@ -322,6 +328,7 @@ func (s *Settings) NamedAuthProviderConfigs() map[string]AuthProviderConfig {
auth.NameOIDC + "3": s.OIDC3Auth,
auth.NameApple: s.AppleAuth,
auth.NameInstagram: s.InstagramAuth,
auth.NameVK: s.VKAuth,
}
}

Expand Down
8 changes: 8 additions & 0 deletions models/settings/settings_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ func TestSettingsValidate(t *testing.T) {
s.AppleAuth.ClientId = ""
s.InstagramAuth.Enabled = true
s.InstagramAuth.ClientId = ""
s.VKAuth.Enabled = true
s.VKAuth.ClientId = ""

// check if Validate() is triggering the members validate methods.
err := s.Validate()
Expand Down Expand Up @@ -108,6 +110,7 @@ func TestSettingsValidate(t *testing.T) {
`"oidc3Auth":{`,
`"appleAuth":{`,
`"instagramAuth":{`,
`"vkAuth":{`,
}

errBytes, _ := json.Marshal(err)
Expand Down Expand Up @@ -177,6 +180,8 @@ func TestSettingsMerge(t *testing.T) {
s2.AppleAuth.ClientId = "apple_test"
s2.InstagramAuth.Enabled = true
s2.InstagramAuth.ClientId = "instagram_test"
s2.VKAuth.Enabled = true
s2.VKAuth.ClientId = "vk_test"

if err := s1.Merge(s2); err != nil {
t.Fatal(err)
Expand Down Expand Up @@ -265,6 +270,7 @@ func TestSettingsRedactClone(t *testing.T) {
s1.OIDC3Auth.ClientSecret = testSecret
s1.AppleAuth.ClientSecret = testSecret
s1.InstagramAuth.ClientSecret = testSecret
s1.VKAuth.ClientSecret = testSecret

s1Bytes, err := json.Marshal(s1)
if err != nil {
Expand Down Expand Up @@ -321,6 +327,7 @@ func TestNamedAuthProviderConfigs(t *testing.T) {
s.OIDC3Auth.ClientId = "oidc3_test"
s.AppleAuth.ClientId = "apple_test"
s.InstagramAuth.ClientId = "instagram_test"
s.VKAuth.ClientId = "vk_test"

result := s.NamedAuthProviderConfigs()

Expand Down Expand Up @@ -350,6 +357,7 @@ func TestNamedAuthProviderConfigs(t *testing.T) {
`"oidc3":{"enabled":false,"clientId":"oidc3_test"`,
`"apple":{"enabled":false,"clientId":"apple_test"`,
`"instagram":{"enabled":false,"clientId":"instagram_test"`,
`"vk":{"enabled":false,"clientId":"vk_test"`,
}
for _, p := range expectedParts {
if !strings.Contains(encodedStr, p) {
Expand Down
2 changes: 2 additions & 0 deletions tools/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ func NewProviderByName(name string) (Provider, error) {
return NewAppleProvider(), nil
case NameInstagram:
return NewInstagramProvider(), nil
case NameVK:
return NewVKProvider(), nil
default:
return nil, errors.New("Missing provider " + name)
}
Expand Down
9 changes: 9 additions & 0 deletions tools/auth/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,4 +189,13 @@ func TestNewProviderByName(t *testing.T) {
if _, ok := p.(*auth.Instagram); !ok {
t.Error("Expected to be instance of *auth.Instagram")
}

// vk
p, err = auth.NewProviderByName(auth.NameVK)
if err != nil {
t.Errorf("Expected nil, got error %v", err)
}
if _, ok := p.(*auth.VK); !ok {
t.Error("Expected to be instance of *auth.VK")
}
}
85 changes: 85 additions & 0 deletions tools/auth/vk.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package auth

import (
"context"
"encoding/json"
"errors"
"fmt"
"strconv"
"strings"

"golang.org/x/oauth2"
"golang.org/x/oauth2/vk"
)

var _ Provider = (*VK)(nil)

// NameVK is the unique name of the VK provider.
const NameVK string = "vk"

// VK allows authentication via VK OAuth2.
type VK struct {
*baseProvider
}

// NewVKProvider creates new VK provider instance with some defaults.
//
// Docs: https://dev.vk.com/api/oauth-parameters
func NewVKProvider() *VK {
return &VK{&baseProvider{
ctx: context.Background(),
scopes: []string{"email"},
authUrl: vk.Endpoint.AuthURL,
tokenUrl: vk.Endpoint.TokenURL,
userApiUrl: "https://api.vk.com/method/users.get?fields=photo_max,screen_name&v=5.131",
}}
}

// FetchAuthUser returns an AuthUser instance based on VK's user api.
//
// API reference: https://dev.vk.com/method/users.get
func (p *VK) FetchAuthUser(token *oauth2.Token) (*AuthUser, error) {
data, err := p.FetchRawUserData(token)
if err != nil {
return nil, err
}

rawUser := map[string]any{}
if err := json.Unmarshal(data, &rawUser); err != nil {
return nil, err
}

extracted := struct {
Response []struct {
Id int `json:"id"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Username string `json:"screen_name"`
AvatarUrl string `json:"photo_max"`
} `json:"response"`
}{}

if err := json.Unmarshal(data, &extracted); err != nil {
return nil, err
}

if len(extracted.Response) == 0 {
return nil, errors.New("missing response entry")
}

user := &AuthUser{
Id: strconv.Itoa(extracted.Response[0].Id),
Name: strings.TrimSpace(extracted.Response[0].FirstName + " " + extracted.Response[0].LastName),
Username: extracted.Response[0].Username,
AvatarUrl: extracted.Response[0].AvatarUrl,
RawUser: rawUser,
AccessToken: token.AccessToken,
RefreshToken: token.RefreshToken,
}

if email := token.Extra("email"); email != nil {
user.Email = fmt.Sprint(email)
}

return user, nil
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import{S as $e,i as Se,s as ye,e as c,w,b as k,c as oe,f as h,g as d,h as a,m as se,x as G,N as we,P as Te,k as je,Q as Ae,n as Be,t as U,a as W,o as u,d as ae,T as Fe,C as Oe,p as Qe,r as V,u as Ne,M as He}from"./index-62348451.js";import{S as Ke}from"./SdkTabs-073ce5a1.js";import{F as qe}from"./FieldsQueryParam-ef685192.js";function ve(n,l,o){const s=n.slice();return s[5]=l[o],s}function Ce(n,l,o){const s=n.slice();return s[5]=l[o],s}function Pe(n,l){let o,s=l[5].code+"",_,p,i,f;function m(){return l[4](l[5])}return{key:n,first:null,c(){o=c("button"),_=w(s),p=k(),h(o,"class","tab-item"),V(o,"active",l[1]===l[5].code),this.first=o},m(v,C){d(v,o,C),a(o,_),a(o,p),i||(f=Ne(o,"click",m),i=!0)},p(v,C){l=v,C&4&&s!==(s=l[5].code+"")&&G(_,s),C&6&&V(o,"active",l[1]===l[5].code)},d(v){v&&u(o),i=!1,f()}}}function Me(n,l){let o,s,_,p;return s=new He({props:{content:l[5].body}}),{key:n,first:null,c(){o=c("div"),oe(s.$$.fragment),_=k(),h(o,"class","tab-item"),V(o,"active",l[1]===l[5].code),this.first=o},m(i,f){d(i,o,f),se(s,o,null),a(o,_),p=!0},p(i,f){l=i;const m={};f&4&&(m.content=l[5].body),s.$set(m),(!p||f&6)&&V(o,"active",l[1]===l[5].code)},i(i){p||(U(s.$$.fragment,i),p=!0)},o(i){W(s.$$.fragment,i),p=!1},d(i){i&&u(o),ae(s)}}}function ze(n){var _e,be;let l,o,s=n[0].name+"",_,p,i,f,m,v,C,H=n[0].name+"",L,ne,E,P,I,j,J,M,K,ie,q,A,ce,Y,z=n[0].name+"",X,re,R,B,Z,$,x,de,ee,T,te,F,le,S,O,g=[],ue=new Map,fe,Q,b=[],me=new Map,y;P=new Ke({props:{js:`
import{S as $e,i as Se,s as ye,e as c,w,b as k,c as oe,f as h,g as d,h as a,m as se,x as G,N as we,P as Te,k as je,Q as Ae,n as Be,t as U,a as W,o as u,d as ae,T as Fe,C as Oe,p as Qe,r as V,u as Ne,M as He}from"./index-328b0666.js";import{S as Ke}from"./SdkTabs-925341d7.js";import{F as qe}from"./FieldsQueryParam-0ca163a0.js";function ve(n,l,o){const s=n.slice();return s[5]=l[o],s}function Ce(n,l,o){const s=n.slice();return s[5]=l[o],s}function Pe(n,l){let o,s=l[5].code+"",_,p,i,f;function m(){return l[4](l[5])}return{key:n,first:null,c(){o=c("button"),_=w(s),p=k(),h(o,"class","tab-item"),V(o,"active",l[1]===l[5].code),this.first=o},m(v,C){d(v,o,C),a(o,_),a(o,p),i||(f=Ne(o,"click",m),i=!0)},p(v,C){l=v,C&4&&s!==(s=l[5].code+"")&&G(_,s),C&6&&V(o,"active",l[1]===l[5].code)},d(v){v&&u(o),i=!1,f()}}}function Me(n,l){let o,s,_,p;return s=new He({props:{content:l[5].body}}),{key:n,first:null,c(){o=c("div"),oe(s.$$.fragment),_=k(),h(o,"class","tab-item"),V(o,"active",l[1]===l[5].code),this.first=o},m(i,f){d(i,o,f),se(s,o,null),a(o,_),p=!0},p(i,f){l=i;const m={};f&4&&(m.content=l[5].body),s.$set(m),(!p||f&6)&&V(o,"active",l[1]===l[5].code)},i(i){p||(U(s.$$.fragment,i),p=!0)},o(i){W(s.$$.fragment,i),p=!1},d(i){i&&u(o),ae(s)}}}function ze(n){var _e,be;let l,o,s=n[0].name+"",_,p,i,f,m,v,C,H=n[0].name+"",L,ne,E,P,I,j,J,M,K,ie,q,A,ce,Y,z=n[0].name+"",X,re,R,B,Z,$,x,de,ee,T,te,F,le,S,O,g=[],ue=new Map,fe,Q,b=[],me=new Map,y;P=new Ke({props:{js:`
import PocketBase from 'pocketbase';
const pb = new PocketBase('${n[3]}');
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import{S as Ue,i as je,s as xe,M as Qe,e as s,w as k,b as p,c as J,f as b,g as d,h as o,m as K,x as ce,N as Oe,P as Je,k as Ke,Q as Ie,n as We,t as N,a as V,o as u,d as I,T as Ge,C as Ee,p as Xe,r as W,u as Ye}from"./index-62348451.js";import{S as Ze}from"./SdkTabs-073ce5a1.js";import{F as et}from"./FieldsQueryParam-ef685192.js";function Le(r,l,a){const n=r.slice();return n[5]=l[a],n}function Ne(r,l,a){const n=r.slice();return n[5]=l[a],n}function Ve(r,l){let a,n=l[5].code+"",m,_,i,f;function v(){return l[4](l[5])}return{key:r,first:null,c(){a=s("button"),m=k(n),_=p(),b(a,"class","tab-item"),W(a,"active",l[1]===l[5].code),this.first=a},m($,w){d($,a,w),o(a,m),o(a,_),i||(f=Ye(a,"click",v),i=!0)},p($,w){l=$,w&4&&n!==(n=l[5].code+"")&&ce(m,n),w&6&&W(a,"active",l[1]===l[5].code)},d($){$&&u(a),i=!1,f()}}}function ze(r,l){let a,n,m,_;return n=new Qe({props:{content:l[5].body}}),{key:r,first:null,c(){a=s("div"),J(n.$$.fragment),m=p(),b(a,"class","tab-item"),W(a,"active",l[1]===l[5].code),this.first=a},m(i,f){d(i,a,f),K(n,a,null),o(a,m),_=!0},p(i,f){l=i;const v={};f&4&&(v.content=l[5].body),n.$set(v),(!_||f&6)&&W(a,"active",l[1]===l[5].code)},i(i){_||(N(n.$$.fragment,i),_=!0)},o(i){V(n.$$.fragment,i),_=!1},d(i){i&&u(a),I(n)}}}function tt(r){var qe,De;let l,a,n=r[0].name+"",m,_,i,f,v,$,w,B,G,S,z,de,Q,q,ue,X,U=r[0].name+"",Y,pe,fe,j,Z,D,ee,T,te,he,F,C,oe,be,le,me,h,_e,R,ke,ve,$e,ae,ge,se,ye,Se,we,ne,Te,Ce,M,re,H,ie,P,O,y=[],Pe=new Map,Re,E,g=[],Me=new Map,A;$=new Ze({props:{js:`
import{S as Ue,i as je,s as xe,M as Qe,e as s,w as k,b as p,c as J,f as b,g as d,h as o,m as K,x as ce,N as Oe,P as Je,k as Ke,Q as Ie,n as We,t as N,a as V,o as u,d as I,T as Ge,C as Ee,p as Xe,r as W,u as Ye}from"./index-328b0666.js";import{S as Ze}from"./SdkTabs-925341d7.js";import{F as et}from"./FieldsQueryParam-0ca163a0.js";function Le(r,l,a){const n=r.slice();return n[5]=l[a],n}function Ne(r,l,a){const n=r.slice();return n[5]=l[a],n}function Ve(r,l){let a,n=l[5].code+"",m,_,i,f;function v(){return l[4](l[5])}return{key:r,first:null,c(){a=s("button"),m=k(n),_=p(),b(a,"class","tab-item"),W(a,"active",l[1]===l[5].code),this.first=a},m($,w){d($,a,w),o(a,m),o(a,_),i||(f=Ye(a,"click",v),i=!0)},p($,w){l=$,w&4&&n!==(n=l[5].code+"")&&ce(m,n),w&6&&W(a,"active",l[1]===l[5].code)},d($){$&&u(a),i=!1,f()}}}function ze(r,l){let a,n,m,_;return n=new Qe({props:{content:l[5].body}}),{key:r,first:null,c(){a=s("div"),J(n.$$.fragment),m=p(),b(a,"class","tab-item"),W(a,"active",l[1]===l[5].code),this.first=a},m(i,f){d(i,a,f),K(n,a,null),o(a,m),_=!0},p(i,f){l=i;const v={};f&4&&(v.content=l[5].body),n.$set(v),(!_||f&6)&&W(a,"active",l[1]===l[5].code)},i(i){_||(N(n.$$.fragment,i),_=!0)},o(i){V(n.$$.fragment,i),_=!1},d(i){i&&u(a),I(n)}}}function tt(r){var qe,De;let l,a,n=r[0].name+"",m,_,i,f,v,$,w,B,G,S,z,de,Q,q,ue,X,U=r[0].name+"",Y,pe,fe,j,Z,D,ee,T,te,he,F,C,oe,be,le,me,h,_e,R,ke,ve,$e,ae,ge,se,ye,Se,we,ne,Te,Ce,M,re,H,ie,P,O,y=[],Pe=new Map,Re,E,g=[],Me=new Map,A;$=new Ze({props:{js:`
import PocketBase from 'pocketbase';
const pb = new PocketBase('${r[3]}');
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import{S as Ve,i as Le,s as Ee,M as je,e as s,w as k,b as h,c as z,f as p,g as r,h as a,m as I,x as he,N as xe,P as Je,k as Ne,Q as Qe,n as ze,t as V,a as L,o as c,d as K,T as Ie,C as We,p as Ke,r as G,u as Ge}from"./index-62348451.js";import{S as Xe}from"./SdkTabs-073ce5a1.js";import{F as Ye}from"./FieldsQueryParam-ef685192.js";function Ue(i,l,o){const n=i.slice();return n[5]=l[o],n}function Be(i,l,o){const n=i.slice();return n[5]=l[o],n}function Fe(i,l){let o,n=l[5].code+"",m,g,u,b;function _(){return l[4](l[5])}return{key:i,first:null,c(){o=s("button"),m=k(n),g=h(),p(o,"class","tab-item"),G(o,"active",l[1]===l[5].code),this.first=o},m(v,A){r(v,o,A),a(o,m),a(o,g),u||(b=Ge(o,"click",_),u=!0)},p(v,A){l=v,A&4&&n!==(n=l[5].code+"")&&he(m,n),A&6&&G(o,"active",l[1]===l[5].code)},d(v){v&&c(o),u=!1,b()}}}function He(i,l){let o,n,m,g;return n=new je({props:{content:l[5].body}}),{key:i,first:null,c(){o=s("div"),z(n.$$.fragment),m=h(),p(o,"class","tab-item"),G(o,"active",l[1]===l[5].code),this.first=o},m(u,b){r(u,o,b),I(n,o,null),a(o,m),g=!0},p(u,b){l=u;const _={};b&4&&(_.content=l[5].body),n.$set(_),(!g||b&6)&&G(o,"active",l[1]===l[5].code)},i(u){g||(V(n.$$.fragment,u),g=!0)},o(u){L(n.$$.fragment,u),g=!1},d(u){u&&c(o),K(n)}}}function Ze(i){let l,o,n=i[0].name+"",m,g,u,b,_,v,A,P,X,S,E,pe,J,M,be,Y,N=i[0].name+"",Z,fe,ee,R,te,x,ae,W,le,y,oe,me,U,$,se,ge,ne,ke,f,_e,C,ve,we,Oe,ie,Ae,re,Se,ye,$e,ce,Te,Ce,q,ue,B,de,T,F,O=[],qe=new Map,De,H,w=[],Pe=new Map,D;v=new Xe({props:{js:`
import{S as Ve,i as Le,s as Ee,M as je,e as s,w as k,b as h,c as z,f as p,g as r,h as a,m as I,x as he,N as xe,P as Je,k as Ne,Q as Qe,n as ze,t as V,a as L,o as c,d as K,T as Ie,C as We,p as Ke,r as G,u as Ge}from"./index-328b0666.js";import{S as Xe}from"./SdkTabs-925341d7.js";import{F as Ye}from"./FieldsQueryParam-0ca163a0.js";function Ue(i,l,o){const n=i.slice();return n[5]=l[o],n}function Be(i,l,o){const n=i.slice();return n[5]=l[o],n}function Fe(i,l){let o,n=l[5].code+"",m,g,u,b;function _(){return l[4](l[5])}return{key:i,first:null,c(){o=s("button"),m=k(n),g=h(),p(o,"class","tab-item"),G(o,"active",l[1]===l[5].code),this.first=o},m(v,A){r(v,o,A),a(o,m),a(o,g),u||(b=Ge(o,"click",_),u=!0)},p(v,A){l=v,A&4&&n!==(n=l[5].code+"")&&he(m,n),A&6&&G(o,"active",l[1]===l[5].code)},d(v){v&&c(o),u=!1,b()}}}function He(i,l){let o,n,m,g;return n=new je({props:{content:l[5].body}}),{key:i,first:null,c(){o=s("div"),z(n.$$.fragment),m=h(),p(o,"class","tab-item"),G(o,"active",l[1]===l[5].code),this.first=o},m(u,b){r(u,o,b),I(n,o,null),a(o,m),g=!0},p(u,b){l=u;const _={};b&4&&(_.content=l[5].body),n.$set(_),(!g||b&6)&&G(o,"active",l[1]===l[5].code)},i(u){g||(V(n.$$.fragment,u),g=!0)},o(u){L(n.$$.fragment,u),g=!1},d(u){u&&c(o),K(n)}}}function Ze(i){let l,o,n=i[0].name+"",m,g,u,b,_,v,A,P,X,S,E,pe,J,M,be,Y,N=i[0].name+"",Z,fe,ee,R,te,x,ae,W,le,y,oe,me,U,$,se,ge,ne,ke,f,_e,C,ve,we,Oe,ie,Ae,re,Se,ye,$e,ce,Te,Ce,q,ue,B,de,T,F,O=[],qe=new Map,De,H,w=[],Pe=new Map,D;v=new Xe({props:{js:`
import PocketBase from 'pocketbase';
const pb = new PocketBase('${i[3]}');
Expand Down
Loading

0 comments on commit af71b63

Please sign in to comment.