Skip to content

Commit 1aed4a2

Browse files
authored
feat: enhance login analytics (#2078)
## Summary Enhance login analytics mainly by adding missing provider information in Supabase Auth. This ensures all authentication flows are properly tracked with structured data.
1 parent 963a781 commit 1aed4a2

File tree

11 files changed

+172
-11
lines changed

11 files changed

+172
-11
lines changed

internal/api/anonymous.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,6 @@ func (a *API) SignupAnonymously(w http.ResponseWriter, r *http.Request) error {
5454
return apierrors.NewInternalServerError("Database error creating anonymous user").WithInternalError(err)
5555
}
5656

57-
metering.RecordLogin("anonymous", newUser.ID)
57+
metering.RecordLogin(metering.LoginTypeAnonymous, newUser.ID, nil)
5858
return sendJSON(w, http.StatusOK, token)
5959
}

internal/api/external.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/supabase/auth/internal/api/apierrors"
1616
"github.com/supabase/auth/internal/api/provider"
1717
"github.com/supabase/auth/internal/conf"
18+
"github.com/supabase/auth/internal/metering"
1819
"github.com/supabase/auth/internal/models"
1920
"github.com/supabase/auth/internal/observability"
2021
"github.com/supabase/auth/internal/storage"
@@ -252,6 +253,13 @@ func (a *API) internalExternalProviderCallback(w http.ResponseWriter, r *http.Re
252253
return err
253254
}
254255

256+
// Record login for analytics - only when token is issued (not during pkce authorize)
257+
if token != nil {
258+
metering.RecordLogin(metering.LoginTypeOAuth, user.ID, &metering.LoginData{
259+
Provider: providerType,
260+
})
261+
}
262+
255263
rurl := a.getExternalRedirectURL(r)
256264
if flowState != nil {
257265
// This means that the callback is using PKCE

internal/api/mfa.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -722,7 +722,10 @@ func (a *API) verifyTOTPFactor(w http.ResponseWriter, r *http.Request, params *V
722722
if err != nil {
723723
return err
724724
}
725-
metering.RecordLogin(string(models.MFACodeLoginAction), user.ID)
725+
726+
metering.RecordLogin(metering.LoginTypeMFA, user.ID, &metering.LoginData{
727+
Provider: metering.ProviderMFATOTP,
728+
})
726729

727730
return sendJSON(w, http.StatusOK, token)
728731

@@ -850,7 +853,10 @@ func (a *API) verifyPhoneFactor(w http.ResponseWriter, r *http.Request, params *
850853
if err != nil {
851854
return err
852855
}
853-
metering.RecordLogin(string(models.MFACodeLoginAction), user.ID)
856+
857+
metering.RecordLogin(metering.LoginTypeMFA, user.ID, &metering.LoginData{
858+
Provider: metering.ProviderMFAPhone,
859+
})
854860

855861
return sendJSON(w, http.StatusOK, token)
856862
}
@@ -950,7 +956,10 @@ func (a *API) verifyWebAuthnFactor(w http.ResponseWriter, r *http.Request, param
950956
if err != nil {
951957
return err
952958
}
953-
metering.RecordLogin(string(models.MFACodeLoginAction), user.ID)
959+
960+
metering.RecordLogin(metering.LoginTypeMFA, user.ID, &metering.LoginData{
961+
Provider: metering.ProviderMFAWebAuthn,
962+
})
954963

955964
return sendJSON(w, http.StatusOK, token)
956965
}

internal/api/samlacs.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/gofrs/uuid"
1515
"github.com/supabase/auth/internal/api/apierrors"
1616
"github.com/supabase/auth/internal/api/provider"
17+
"github.com/supabase/auth/internal/metering"
1718
"github.com/supabase/auth/internal/models"
1819
"github.com/supabase/auth/internal/observability"
1920
"github.com/supabase/auth/internal/storage"
@@ -328,6 +329,14 @@ func (a *API) handleSamlAcs(w http.ResponseWriter, r *http.Request) error {
328329
return nil
329330

330331
}
332+
333+
// Record login for analytics - only when token is issued (not during pkce authorize)
334+
if token != nil {
335+
metering.RecordLogin(metering.LoginTypeSSO, token.User.ID, &metering.LoginData{
336+
Provider: metering.ProviderSAML,
337+
})
338+
}
339+
331340
http.Redirect(w, r, token.AsRedirectURL(redirectTo, url.Values{}), http.StatusFound)
332341

333342
return nil

internal/api/signup.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,13 @@ func (a *API) Signup(w http.ResponseWriter, r *http.Request) error {
322322
if err != nil {
323323
return err
324324
}
325-
metering.RecordLogin("password", user.ID)
325+
metering.RecordLogin(metering.LoginTypePassword, user.ID, &metering.LoginData{
326+
Provider: params.Provider,
327+
// add extra context to indicate this is immediate login right after signup
328+
Extra: map[string]interface{}{
329+
"immediate_login_after_signup": true,
330+
},
331+
})
326332
return sendJSON(w, http.StatusOK, token)
327333
}
328334
if user.HasBeenInvited() {

internal/api/token.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,9 @@ func (a *API) ResourceOwnerPasswordGrant(ctx context.Context, w http.ResponseWri
243243

244244
token.WeakPassword = weakPasswordError
245245

246-
metering.RecordLogin("password", user.ID)
246+
metering.RecordLogin(metering.LoginTypePassword, user.ID, &metering.LoginData{
247+
Provider: provider,
248+
})
247249
return sendJSON(w, http.StatusOK, token)
248250
}
249251

@@ -318,6 +320,9 @@ func (a *API) PKCE(ctx context.Context, w http.ResponseWriter, r *http.Request)
318320
return err
319321
}
320322

323+
metering.RecordLogin(metering.LoginTypePKCE, user.ID, &metering.LoginData{
324+
Provider: flowState.ProviderType,
325+
})
321326
return sendJSON(w, http.StatusOK, token)
322327
}
323328

internal/api/token_oidc.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/supabase/auth/internal/api/apierrors"
1111
"github.com/supabase/auth/internal/api/provider"
1212
"github.com/supabase/auth/internal/conf"
13+
"github.com/supabase/auth/internal/metering"
1314
"github.com/supabase/auth/internal/models"
1415
"github.com/supabase/auth/internal/observability"
1516
"github.com/supabase/auth/internal/storage"
@@ -280,5 +281,9 @@ func (a *API) IdTokenGrant(ctx context.Context, w http.ResponseWriter, r *http.R
280281
}
281282
}
282283

284+
metering.RecordLogin(metering.LoginTypeOIDC, token.User.ID, &metering.LoginData{
285+
Provider: providerType,
286+
})
287+
283288
return sendJSON(w, http.StatusOK, token)
284289
}

internal/api/token_refresh.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ func (a *API) RefreshTokenGrant(ctx context.Context, w http.ResponseWriter, r *h
276276
return err
277277
}
278278
}
279-
metering.RecordLogin("token", user.ID)
279+
metering.RecordLogin(metering.LoginTypeToken, user.ID, nil)
280280
return sendJSON(w, http.StatusOK, newTokenResponse)
281281
}
282282

internal/api/verify.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"github.com/supabase/auth/internal/api/sms_provider"
1717
"github.com/supabase/auth/internal/crypto"
1818
mail "github.com/supabase/auth/internal/mailer"
19+
"github.com/supabase/auth/internal/metering"
1920
"github.com/supabase/auth/internal/models"
2021
"github.com/supabase/auth/internal/observability"
2122
"github.com/supabase/auth/internal/storage"
@@ -211,6 +212,9 @@ func (a *API) verifyGet(w http.ResponseWriter, r *http.Request, params *VerifyPa
211212
q := url.Values{}
212213
q.Set("type", params.Type)
213214
rurl = token.AsRedirectURL(rurl, q)
215+
216+
metering.RecordLogin(metering.LoginTypeImplicit, token.User.ID, nil)
217+
214218
} else if isPKCEFlow(flowType) {
215219
rurl, err = a.prepPKCERedirectURL(rurl, authCode)
216220
if err != nil {
@@ -292,6 +296,16 @@ func (a *API) verifyPost(w http.ResponseWriter, r *http.Request, params *VerifyP
292296
"code": strconv.Itoa(http.StatusOK),
293297
})
294298
}
299+
300+
// Record login for analytics - determine provider based on verification type
301+
provider := metering.ProviderEmail // default
302+
if params.Type == smsVerification || params.Type == phoneChangeVerification {
303+
provider = metering.ProviderPhone
304+
}
305+
metering.RecordLogin(metering.LoginTypeOTP, user.ID, &metering.LoginData{
306+
Provider: provider,
307+
})
308+
295309
return sendJSON(w, http.StatusOK, token)
296310
}
297311

internal/api/web3.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88

99
"github.com/supabase/auth/internal/api/apierrors"
1010
"github.com/supabase/auth/internal/api/provider"
11+
"github.com/supabase/auth/internal/metering"
1112
"github.com/supabase/auth/internal/models"
1213
"github.com/supabase/auth/internal/storage"
1314
"github.com/supabase/auth/internal/utilities"
@@ -167,5 +168,16 @@ func (a *API) web3GrantSolana(ctx context.Context, w http.ResponseWriter, r *htt
167168
}
168169
}
169170

171+
// Record login for analytics with Web3 context
172+
metering.RecordLogin(metering.LoginTypeWeb3, token.User.ID, &metering.LoginData{
173+
Web3: &metering.Web3Data{
174+
Chain: params.Chain,
175+
Network: parsedMessage.ChainID,
176+
Address: parsedMessage.Address,
177+
Domain: parsedMessage.Domain,
178+
URI: parsedMessage.URI.String(),
179+
},
180+
})
181+
170182
return sendJSON(w, http.StatusOK, token)
171183
}

0 commit comments

Comments
 (0)