-
-
Save matevarga/daf2273ddea0644ba6a6e91ae22442e0 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
func initUser(app *pocketbase.PocketBase, email string) (*models.Record, error) { | |
log.Println("initUser", email) | |
// Check if the user already exists | |
existingUser, _ := app.Dao().FindAuthRecordByEmail("users", email) | |
if existingUser != nil { | |
// User already exists | |
return existingUser, nil | |
} | |
// Create a new user | |
userCollection, err := app.Dao().FindCollectionByNameOrId("users") | |
if err != nil { | |
return nil, err | |
} | |
newUser := models.NewRecord(userCollection) | |
newUser.Set("email", email) | |
newUser.Set("verified", false) | |
newUser.Set("username", email) | |
// Save the new user | |
if err := app.Dao().SaveRecord(newUser); err != nil { | |
return nil, err | |
} | |
return newUser, nil | |
} | |
func registerOtpHandler(app *pocketbase.PocketBase) { | |
app.OnBeforeServe().Add(func(e *core.ServeEvent) error { | |
// TODO: add a cron job to delete expired otp_auth records | |
// This is not a security related and can be done occasionally. | |
// Step 1: User requests an OTP token | |
// | |
// The otp-auth endpoint is used to generate an otp_auth record. If the user's email | |
// is found, the OTP is stored, emailed, and verifyToken (this record's ID) is returned | |
// to the caller. | |
e.Router.POST("/otp-auth", func(c echo.Context) error { | |
data := apis.RequestInfo(c).Data | |
log.Println("data", data) | |
email, ok := data["email"].(string) | |
if !ok { | |
return badEmailErr | |
} | |
user, err := app.Dao().FindAuthRecordByEmail("users", email) | |
if user == nil { | |
user, err = initUser(app, email) | |
if err != nil { | |
return err | |
} | |
} | |
collection, err := app.Dao().FindCollectionByNameOrId("otp_auth") | |
if err != nil { | |
log.Println("find collection error", err) | |
return apis.NewBadRequestError("", nil) | |
} | |
record := models.NewRecord(collection) | |
record.Set("user", user.GetId()) | |
record.Set("expiration", time.Now().Add(time.Minute*60)) | |
record.Set("creation", time.Now()) | |
otp := security.RandomStringWithAlphabet(6, "0123456789") | |
record.Set("otp", otp) | |
log.Println("otp", otp) // For testing purposes. Normally, you would send this to the user's email. | |
if err := app.Dao().SaveRecord(record); err != nil { | |
log.Println("save error", err) | |
return apis.NewBadRequestError("", nil) | |
} | |
return c.JSON(200, echo.Map{ | |
"verifyToken": record.GetId(), | |
}) | |
}) | |
// Step 2: User submits the OTP token for verification | |
// | |
// The otp-verify endpoint is used to verify the OTP token. If the token is valid, | |
// the user is authenticated and the standard RecordAuthResponse is returned. | |
e.Router.POST("/otp-verify", func(c echo.Context) error { | |
data := struct { | |
VerifyToken string `json:"verifyToken"` | |
OTP string `json:"otp"` | |
}{} | |
if err := c.Bind(&data); err != nil { | |
log.Println("bind error", err) | |
return unauthorizedErr | |
} | |
record, err := app.Dao().FindRecordById("otp_auth", data.VerifyToken) | |
if err != nil { | |
return unauthorizedErr | |
} | |
// validate expiration | |
if record.GetDateTime("expiration").Time().Before(time.Now()) { | |
app.Dao().DeleteRecord(record) | |
return unauthorizedErr | |
} | |
// validate otp | |
if !security.Equal(record.GetString("otp"), data.OTP) { | |
attempts := record.GetInt("attempts") + 1 | |
if attempts > 3 { | |
app.Dao().DeleteRecord(record) | |
return unauthorizedErr | |
} | |
record.Set("attempts", attempts) | |
if err := app.Dao().SaveRecord(record); err != nil { | |
log.Println("save error", err) | |
} | |
return unauthorizedErr | |
} | |
// At this point the OTP record is consumed and should be deleted | |
defer app.Dao().DeleteRecord(record) | |
if err := app.Dao().ExpandRecord(record, []string{"user"}, nil); len(err) > 0 { | |
log.Println("expand error", err) | |
return unauthorizedErr | |
} | |
user := record.ExpandedOne("user") | |
if user == nil { | |
return unauthorizedErr | |
} | |
user.SetVerified(true) | |
app.Dao().SaveRecord(user) | |
return apis.RecordAuthResponse(app, c, user, nil) | |
}) | |
return nil | |
}) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment