diff --git a/client/packages/core/__tests__/src/auth-extra-fields.e2e.test.ts b/client/packages/core/__tests__/src/auth-extra-fields.e2e.test.ts index 0e37b7a750..648782de8b 100644 --- a/client/packages/core/__tests__/src/auth-extra-fields.e2e.test.ts +++ b/client/packages/core/__tests__/src/auth-extra-fields.e2e.test.ts @@ -65,6 +65,21 @@ authTest( }, ); +// authTest('guest user with extraFields gets fields written', async ({ db }) => { +// const res = await db.auth.signInAsGuest({ +// extraFields: { username: 'guest_user', displayName: 'Guest User' }, +// }); +// +// expect(res.user.isGuest).toBe(true); +// +// const { data } = await db.queryOnce({ $users: {} }); +// const user = data.$users.find((u) => u.id === res.user.id); +// expect(user).toMatchObject({ +// username: 'guest_user', +// displayName: 'Guest User', +// }); +// }); + authTest( 'returning user gets created=false', async ({ db, appId, adminToken }) => { diff --git a/client/packages/core/src/Reactor.js b/client/packages/core/src/Reactor.js index aab474ef46..0a07adcf65 100644 --- a/client/packages/core/src/Reactor.js +++ b/client/packages/core/src/Reactor.js @@ -2378,10 +2378,11 @@ export default class Reactor { return res; } - async signInAsGuest() { + async signInAsGuest(params) { const res = await authAPI.signInAsGuest({ apiURI: this.config.apiURI, appId: this.config.appId, + extraFields: params?.extraFields, }); await this.changeCurrentUser(res.user); return res; diff --git a/client/packages/core/src/authAPI.ts b/client/packages/core/src/authAPI.ts index ae72b08c80..1656addbed 100644 --- a/client/packages/core/src/authAPI.ts +++ b/client/packages/core/src/authAPI.ts @@ -105,15 +105,20 @@ export async function verifyRefreshToken({ return res; } +export type SignInAsGuestParams = { + extraFields?: Record | undefined; +}; export async function signInAsGuest({ apiURI, appId, -}: SharedInput): Promise { + extraFields, +}: SharedInput & SignInAsGuestParams): Promise { const res = await jsonFetch(`${apiURI}/runtime/auth/sign_in_guest`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ 'app-id': appId, + ...(extraFields ? { 'extra-fields': extraFields } : {}), }), }); return res; diff --git a/client/packages/core/src/index.ts b/client/packages/core/src/index.ts index ddc4fb3395..219286a44e 100644 --- a/client/packages/core/src/index.ts +++ b/client/packages/core/src/index.ts @@ -126,6 +126,7 @@ import type { ExchangeCodeForTokenParams, SendMagicCodeParams, SendMagicCodeResponse, + SignInAsGuestParams, SignInWithIdTokenParams, VerifyMagicCodeParams, VerifyResponse, @@ -403,8 +404,8 @@ class Auth { * @example * db.auth.signInAsGuest(); */ - signInAsGuest = (): Promise => { - return this.db.signInAsGuest(); + signInAsGuest = (params?: SignInAsGuestParams): Promise => { + return this.db.signInAsGuest(params); }; /** @@ -1209,6 +1210,7 @@ export { type ExchangeCodeForTokenParams, type SendMagicCodeParams, type SendMagicCodeResponse, + type SignInAsGuestParams, type SignInWithIdTokenParams, type VerifyMagicCodeParams, type VerifyResponse, diff --git a/client/packages/version/src/version.ts b/client/packages/version/src/version.ts index c46414d18f..063bd17103 100644 --- a/client/packages/version/src/version.ts +++ b/client/packages/version/src/version.ts @@ -2,6 +2,6 @@ // Update the version here and merge your code to main to // publish a new version of all of the packages to npm. -const version = 'v1.0.59'; +const version = 'v1.0.60'; export { version }; diff --git a/client/www/app/docs/auth/guest-auth/page.md b/client/www/app/docs/auth/guest-auth/page.md index 312b67798f..1f465898f1 100644 --- a/client/www/app/docs/auth/guest-auth/page.md +++ b/client/www/app/docs/auth/guest-auth/page.md @@ -11,6 +11,16 @@ Instant supports guest authentication. This allows your users to try your app be Use `db.auth.signInAsGuest()` to create a new guest user. This will create a new guest user with an id, but no email address. +You can set custom `$users` properties when creating the guest by passing `extraFields`: + +```javascript +db.auth.signInAsGuest({ + extraFields: { nickname: 'nezaj' }, +}); +``` + +The fields must be optional attributes on `$users`, and `$users` must have a `create` rule. See [Setting properties at signup](/docs/users#setting-properties-at-signup) for details. + ```tsx {% showCopy=true %} 'use client'; diff --git a/client/www/app/docs/users/page.md b/client/www/app/docs/users/page.md index 9478039ce6..c203fbbe89 100644 --- a/client/www/app/docs/users/page.md +++ b/client/www/app/docs/users/page.md @@ -180,6 +180,14 @@ You can set custom `$users` properties at the moment a user is created by passin The fields you pass must be defined in your schema as optional attributes on `$users`. +**Guest auth** + +```javascript +db.auth.signInAsGuest({ + extraFields: { nickname: 'nezaj' }, +}); +``` + **Magic codes** ```javascript diff --git a/server/src/instant/admin/routes.clj b/server/src/instant/admin/routes.clj index b323da88ab..9ebf1da15f 100644 --- a/server/src/instant/admin/routes.clj +++ b/server/src/instant/admin/routes.clj @@ -533,13 +533,18 @@ (defn sign-in-guest-post [req] (let [{:keys [app-id]} (req->app-id-authed! req :data/write) - ;; create guest user - user-id (random-uuid) - user (app-user-model/create! - {:app-id app-id - :id user-id - :type "guest"}) - ;; create refresh-token for user + extra-fields (get-in req [:body :extra-fields]) + user-id (random-uuid) + _ (when (seq extra-fields) + (app-user-model/assert-signup! + {:app-id app-id + :extra-fields extra-fields + :skip-perm-check? true})) + user (app-user-model/create! + {:app-id app-id + :id user-id + :type "guest" + :extra-fields extra-fields}) refresh-token (random-uuid) _ (app-user-refresh-token-model/create! {:app-id app-id diff --git a/server/src/instant/runtime/routes.clj b/server/src/instant/runtime/routes.clj index 041878fa32..60cded4520 100644 --- a/server/src/instant/runtime/routes.clj +++ b/server/src/instant/runtime/routes.clj @@ -130,13 +130,17 @@ (defn sign-in-guest-post [req] (let [app-id (ex/get-param! req [:body :app-id] uuid-util/coerce) + extra-fields (get-in req [:body :extra-fields]) user-id (random-uuid) _ (app-user-model/assert-signup! - {:app-id app-id :id user-id}) + {:app-id app-id + :id user-id + :extra-fields extra-fields}) user (app-user-model/create! - {:app-id app-id - :id user-id - :type "guest"}) + {:app-id app-id + :id user-id + :type "guest" + :extra-fields extra-fields}) refresh-token (random-uuid) _ (app-user-refresh-token-model/create! {:app-id app-id diff --git a/server/test/instant/runtime/routes_test.clj b/server/test/instant/runtime/routes_test.clj index 8cc24332eb..cfad024d42 100644 --- a/server/test/instant/runtime/routes_test.clj +++ b/server/test/instant/runtime/routes_test.clj @@ -100,20 +100,25 @@ :body :user)) -(defn sign-in-guest-runtime [app] - (-> (request {:method :post - :url "/runtime/auth/sign_in_guest" - :body {:app-id (:id app)}}) - :body - :user)) - -(defn sign-in-guest-admin [app] - (-> (request {:method :post - :url "/admin/sign_in_guest" - :headers {"app-id" (:id app) - "authorization" (str "Bearer " (:admin-token app))}}) - :body - :user)) +(defn sign-in-guest-runtime + ([app] (sign-in-guest-runtime app {})) + ([app body] + (-> (request {:method :post + :url "/runtime/auth/sign_in_guest" + :body (assoc body :app-id (:id app))}) + :body + :user))) + +(defn sign-in-guest-admin + ([app] (sign-in-guest-admin app {})) + ([app body] + (-> (request {:method :post + :url "/admin/sign_in_guest" + :headers {"app-id" (:id app) + "authorization" (str "Bearer " (:admin-token app))} + :body body}) + :body + :user))) (deftest magic-codes-test (test-util/test-matrix @@ -669,8 +674,9 @@ (rule-model/put! {:app-id app-id :code {"$users" {"allow" {"create" "true"}}}}) - (let [guest (sign-in-guest app) + (let [guest (sign-in-guest app {:extra-fields {"username" "guest_user"}}) _ (is (= "guest" (:type guest))) + _ (is (= "guest_user" (:username guest))) code (send-code app {:email "guest@test.com"}) body (verify-body app {:email "guest@test.com" :code code @@ -883,6 +889,14 @@ (deftest users-create-rule-guest-test (with-empty-app (fn [{app-id :id :as app}] + (test-util/make-attrs app-id + [[:$users/username]]) + + (testing "extra-fields require an explicit create rule" + (is (thrown-with-msg? + ExceptionInfo #"status 400" + (sign-in-guest-runtime app {:extra-fields {"username" "guest_user"}})))) + (testing "create rule blocks guest signup" (rule-model/put! {:app-id app-id :code {"$users" {"allow" {"create" "false"}}}})