Phone verification #350
-
|
I need to send SMS to users. I have added the field Still, I need users to verify their phone numbers before sending the transactional SMS. I have added the following extra fields to my profile: Possibly wrong collection API rules: My current thoughts are:
I have tried to check the source (I am learning Go) and also #279, but I still can't figure out how to correctly design and set up the verification flow. Could someone share some pointers on how I might go about setting up for the same? |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 2 replies
-
|
@aphilas I haven't tested it, since it depends on what SMS API you are using, but you could try creating a package main
import (
"log"
"net/http"
"time"
"github.com/labstack/echo/v5"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/apis"
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/models"
"github.com/pocketbase/pocketbase/tools/rest"
"github.com/pocketbase/pocketbase/tools/security"
"github.com/pocketbase/pocketbase/tools/types"
)
func main() {
app := pocketbase.New()
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
// adds `POST /api/my/request-phone-verification` endpoint
e.Router.AddRoute(echo.Route{
Method: http.MethodPost,
Path: "/api/my/request-phone-verification",
Middlewares: []echo.MiddlewareFunc{
apis.RequireUserAuth(),
},
Handler: func(c echo.Context) error {
user, _ := c.Get(apis.ContextUserKey).(*models.User)
if user == nil {
return rest.NewNotFoundError("Missing auth user context.", nil)
}
// check if it is already verified
if user.Profile.GetBoolDataValue("phoneVerified") {
return rest.NewBadRequestError("The phone number was already verified.", nil)
}
// prevent flooding the user with sms messages
now := time.Now().UTC()
lastVerificationSentAt := user.Profile.GetDateTimeDataValue("lastPhoneVerificationSent").Time()
if (now.Sub(lastVerificationSentAt)).Seconds() < 120 { // 120 seconds
return rest.NewBadRequestError("A verification code was already generated.", nil)
}
phoneNumber := user.Profile.GetStringDataValue("phone")
verificationCode := security.RandomStringWithAlphabet(6, "123456789")
// --- !!! ---
// add here your "Send SMS" call...
log.Println(verificationCode, phoneNumber)
// --- !!! ---
// update user's profile data with the generated code
user.Profile.SetDataValue("lastPhoneVerificationSent", types.NowDateTime())
user.Profile.SetDataValue("phoneVerificationCode", verificationCode)
if err := app.Dao().SaveRecord(user.Profile); err != nil {
return rest.NewBadRequestError("Failed to save user profile.", err)
}
return c.NoContent(http.StatusNoContent)
},
})
// adds `POST /api/my/confirm-phone-verification` endpoint
e.Router.AddRoute(echo.Route{
Method: http.MethodPost,
Path: "/api/my/confirm-phone-verification",
Middlewares: []echo.MiddlewareFunc{
apis.RequireUserAuth(),
},
Handler: func(c echo.Context) error {
user, _ := c.Get(apis.ContextUserKey).(*models.User)
if user == nil {
return rest.NewNotFoundError("Missing auth user context.", nil)
}
if user.Profile.GetBoolDataValue("phoneVerified") {
// already verified, no further actions needed
return c.NoContent(http.StatusNoContent)
}
// get the verification code from the request form data (both multipart and json)
formData := &struct {
Code string `form:"code" json:"code"` // change to whatever request parameter you want
}{}
if readErr := c.Bind(formData); readErr != nil {
return rest.NewBadRequestError("An error occurred while reading the submitted data.", readErr)
}
verificationCode := user.Profile.GetStringDataValue("phoneVerificationCode")
if verificationCode != formData.Code || formData.Code == "" {
return rest.NewBadRequestError("Invalid verification code.", nil)
}
// verification confirmed
user.Profile.SetDataValue("phoneVerified", true)
user.Profile.SetDataValue("phoneVerificationCode", "")
user.Profile.SetDataValue("lastPhoneVerificationSent", "")
if err := app.Dao().SaveRecord(user.Profile); err != nil {
return rest.NewBadRequestError("Failed to save user profile.", err)
}
return c.NoContent(http.StatusNoContent)
},
})
return nil
})
app.OnRecordBeforeCreateRequest().Add(func(e *core.RecordCreateEvent) error {
if e.Record.Collection().Name != models.ProfileCollectionName {
// not a profiles collection
return nil
}
// prevent setting phoneVerified
if e.Record.GetBoolDataValue("phoneVerified") != false {
return rest.NewForbiddenError("You are not allowed to set the phoneVerified value with this action.", nil)
}
// prevent setting lastPhoneVerificationSent
if e.Record.GetStringDataValue("lastPhoneVerificationSent") != "" {
return rest.NewForbiddenError("You are not allowed to set the lastPhoneVerificationSent value with this action.", nil)
}
// prevent setting phoneVerificationCode
if e.Record.GetStringDataValue("phoneVerificationCode") != "" {
return rest.NewForbiddenError("You are not allowed to set the phoneVerificationCode value with this action.", nil)
}
return nil
})
app.OnRecordBeforeUpdateRequest().Add(func(e *core.RecordUpdateEvent) error {
if e.Record.Collection().Name != models.ProfileCollectionName {
// not a profiles collection
return nil
}
originalProfile, err := app.Dao().FindRecordById(e.Record.Collection(), e.Record.Id, nil)
if err != nil {
return err
}
// prevent manual phoneVerified change
if originalProfile.GetBoolDataValue("phoneVerified") != e.Record.GetBoolDataValue("phoneVerified") {
return rest.NewForbiddenError("You are not allowed to update the phoneVerified value with this action.", nil)
}
// prevent manual phoneVerificationCode change
if originalProfile.GetStringDataValue("phoneVerificationCode") != e.Record.GetStringDataValue("phoneVerificationCode") {
return rest.NewForbiddenError("You are not allowed to update the phoneVerificationCode value with this action.", nil)
}
// prevent manual lastPhoneVerificationSent change
if originalProfile.GetStringDataValue("lastPhoneVerificationSent") != e.Record.GetStringDataValue("lastPhoneVerificationSent") {
return rest.NewForbiddenError("You are not allowed to update the lastPhoneVerificationSent value with this action.", nil)
}
// on phone number change, reset the phoneVerified state
if originalProfile.GetStringDataValue("phone") != e.Record.GetStringDataValue("phone") {
e.Record.SetDataValue("phoneVerified", false)
e.Record.SetDataValue("phoneVerificationCode", "")
e.Record.SetDataValue("lastPhoneVerificationSent", "")
}
return nil
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}The above will do 4 things:
I've updated the above example to use a random 6 digit 1-9 code |
Beta Was this translation helpful? Give feedback.
@aphilas I haven't tested it, since it depends on what SMS API you are using, but you could try creating a
main.gofile with the following content: