Open Hemanth-2002 opened 3 months ago
To implement Google sign-in functionality on your website, we need to make changes to both the front end and the back end of the application. The back end will handle the OAuth2 flow, including generating the OAuth2 URL, exchanging the authorization code for tokens, and storing the necessary user information. The front end will handle the user interface for initiating the sign-in process.
authorization/authuser.go
Add fields to the User
struct to store Google OAuth2 information and update the NewUserAuth
function to initialize these fields.
package authorization
// User contains user's information
type User struct {
Username string
Email string
GoogleID string
AccessToken string
RefreshToken string
}
// NewUserAuth returns a new user
func NewUserAuth(username string, email string, googleID string, accessToken string, refreshToken string) (*User, error) {
user := &User{
Username: username,
Email: email,
GoogleID: googleID,
AccessToken: accessToken,
RefreshToken: refreshToken,
}
return user, nil
}
model/model.go
Add fields to the User
struct to store Google OAuth2 information.
package model
import "github.com/jinzhu/gorm"
type User struct {
gorm.Model
UserName string
Password string
Email string
PhoneNumber string
Booking []Booking
GoogleID string // Field to store Google ID
AccessToken string // Field to store Google OAuth2 access token
RefreshToken string // Field to store Google OAuth2 refresh token
}
authorization/jwt_manager.go
Modify the UserClaims
struct and the Generate
and Verify
methods to support Google OAuth2 information.
package authorization
import (
"fmt"
"time"
"github.com/dgrijalva/jwt-go"
)
// JWTManager is a JSON web token manager
type JWTManager struct {
SecretKey string
TokenDuration time.Duration
}
// UserClaims is a custom JWT claims that contains some user's information
type UserClaims struct {
jwt.StandardClaims
Username string `json:"username"`
Email string `json:"email"`
GoogleID string `json:"google_id,omitempty"` // Add GoogleID field
}
// NewJWTManager returns a new JWT manager
func NewJWTManager(secretKey string, tokenDuration time.Duration) *JWTManager {
return &JWTManager{secretKey, tokenDuration}
}
// Generate generates and signs a new token for a user
func (manager *JWTManager) Generate(user *User) (string, error) {
claims := UserClaims{
StandardClaims: jwt.StandardClaims{
ExpiresAt: time.Now().Add(manager.TokenDuration).Unix(),
},
Username: user.Username,
Email: user.Email,
GoogleID: user.GoogleID, // Include GoogleID in the claims
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte(manager.SecretKey))
}
// Verify verifies the access token string and return a user claim if the token is valid
func (manager *JWTManager) Verify(accessToken string) (*UserClaims, error) {
token, err := jwt.ParseWithClaims(
accessToken,
&UserClaims{},
func(token *jwt.Token) (interface{}, error) {
_, ok := token.Method.(*jwt.SigningMethodHMAC)
if !ok {
return nil, fmt.Errorf("unexpected token signing method")
}
return []byte(manager.SecretKey), nil
},
)
if err != nil {
return nil, fmt.Errorf("invalid token: %w", err)
}
claims, ok := token.Claims.(*UserClaims)
if !ok {
return nil, fmt.Errorf("invalid token claims")
}
return claims, nil
}
utils/utils.go
Add utility functions for Google OAuth2.
package utils
import (
"context"
"fmt"
"log"
"net/http"
"os"
"github.com/joho/godotenv"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
)
var googleOAuthConfig *oauth2.Config
func init() {
LoadEnv()
googleOAuthConfig = &oauth2.Config{
RedirectURL: os.Getenv("GOOGLE_REDIRECT_URL"),
ClientID: os.Getenv("GOOGLE_CLIENT_ID"),
ClientSecret: os.Getenv("GOOGLE_CLIENT_SECRET"),
Scopes: []string{"https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/userinfo.profile"},
Endpoint: google.Endpoint,
}
}
func GenerateGoogleOAuthURL(state string) string {
return googleOAuthConfig.AuthCodeURL(state)
}
func ExchangeAuthCodeForToken(code string) (*oauth2.Token, error) {
token, err := googleOAuthConfig.Exchange(context.Background(), code)
if err != nil {
return nil, fmt.Errorf("failed to exchange auth code for token: %v", err)
}
return token, nil
}
func HandleOAuth2Error(w http.ResponseWriter, err error) {
http.Error(w, fmt.Sprintf("OAuth2 error: %v", err), http.StatusInternalServerError)
}
func PanicError(err error) {
if err != nil {
panic(err)
}
}
func CheckError(err error) {
if err != nil {
log.Print(err)
}
}
func CheckCall(err error) {
if err != nil {
fmt.Println("call unsuccessful")
}
}
func LoadEnv() {
envErr := godotenv.Load(".env")
if envErr != nil {
fmt.Printf("Could not load .env file")
os.Exit(1)
}
}
func MailInfo(sender string, amount string, receiver string, coupon string, mode string) string {
jsonInfo := fmt.Sprintf(`{
"personalizations": [
{
"to": [
{
"email": "%s"
}
],
"subject": "Payment Report"
}
],
"from": {
"email": "%s"
},
"content": [
{
"type": "text/plain",
"value": "Your payment is successful !!!\n\nThe following are the payment details:\n\nAmount: %s\n\nDiscount Coupon Number: %s\n\nPayment Mode: %s\n\nThank You For Booking With Us"
}
]
}`, receiver, sender, amount, coupon, mode)
return jsonInfo
}
database/database.go
Ensure that the CreateUser
and UpdateUser
methods handle the new fields.
package database
import (
"bms/model"
"github.com/jinzhu/gorm"
)
type DataBase interface {
CreateUser(model.User) (uint, error)
UpdateUser(model.User) error
GetShow(int) ([]model.Show, error)
UpdateShow(model.Show) error
AddSeat(model.Seat) error
AddPayment(model.Payment) (model.User, error)
AddMovie(model.Movie) error
GetMovies() ([]model.Movie, error)
GetMovie(model.Movie) ([]model.Movie, error)
UpdateMovie(model.Movie) error
AddBooking(model.Booking) error
GetBookings(int) ([]model.Booking, error)
CancelBooking(int) error
}
type DBClient struct {
Db *gorm.DB
}
func (db DBClient) CreateUser(user model.User) (uint, error) {
DB := db.Db.Save(&user)
return user.ID, DB.Error
}
func (db DBClient) UpdateUser(user model.User) error {
DB := db.Db.Save(&user)
return DB.Error
}
func (db DBClient) GetShow(theatreId int) ([]model.Show, error) {
Shows := []model.Show{}
DB := db.Db.Where(&model.Show{TheatreID: int(theatreId)}).Find(&Shows)
return Shows, DB.Error
}
func (db DBClient) UpdateShow(show model.Show) error {
DB := db.Db.Save(&show)
return DB.Error
}
func (db DBClient) AddSeat(seat model.Seat) error {
DB := db.Db.Save(&seat)
return DB.Error
}
func (db DBClient) AddPayment(payment model.Payment) (model.User, error) {
user := model.User{}
db.Db.Save(&payment)
DB := db.Db.Where("id = ?", payment.UserID).Find(&user)
return user, DB.Error
}
func (db DBClient) AddMovie(movie model.Movie) error {
DB := db.Db.Save(&movie)
return DB.Error
}
func (db DBClient) GetMovies() ([]model.Movie, error) {
Movies := []model.Movie{}
DB := db.Db.Find(&Movies)
return Movies, DB.Error
}
func (db DBClient) GetMovie(movie model.Movie) ([]model.Movie, error) {
Movies := []model.Movie{}
DB := db.Db.Model(&Movies).Where(&movie).Find(&Movies)
return Movies, DB.Error
}
func (db DBClient) UpdateMovie(movie model.Movie) error {
DB := db.Db.Save(&movie)
return DB.Error
}
func (db DBClient) AddBooking(booking model.Booking) error {
DB := db.Db.Save(&booking)
return DB.Error
}
func (db DBClient) GetBookings(userId int) ([]model.Booking, error) {
Bookings := []model.Booking{}
DB := db.Db.Where("user_id = ?", userId).Find(&Bookings)
return Bookings, DB.Error
}
func (db DBClient) CancelBooking(bookingId int) error {
DB := db.Db.Model(&model.Booking{}).Where("id=?", bookingId).Delete(&model.Booking{})
return DB.Error
}
server/user.go
Add endpoints for initiating the Google OAuth2 sign-in process and handling the OAuth2 callback.
package server
import (
"context"
"encoding/json"
"log"
"net/http"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
"bms/model"
"bms/utils"
pb "bms/bmsproto"
)
// OAuth2 configuration
var googleOauthConfig = &oauth2.Config{
RedirectURL: "http://localhost:8080/auth/google/callback",
ClientID: "YOUR_GOOGLE_CLIENT_ID",
ClientSecret: "YOUR_GOOGLE_CLIENT_SECRET",
Scopes: []string{"https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/userinfo.profile"},
Endpoint: google.Endpoint,
}
// function to add new user on server
func (s *BmsServer) CreateUser(ctx context.Context, in *pb.NewUser) (*pb.User, error) {
log.Printf("creating new user called")
newUser := model.User{
UserName: in.GetUserName(),
Password: in.GetPassword(),
Email: in.GetEmail(),
PhoneNumber: in.GetPhoneNumber(),
}
userId, err := s.Db.CreateUser(newUser)
utils.CheckCall(err)
if err != nil {
return nil, err
}
return &pb.User{UserName: newUser.UserName, Password: newUser.Password, Email: newUser.Email, PhoneNumber: newUser.PhoneNumber, Id: uint64(userId)}, nil
}
// function to update user info on server
func (s *BmsServer) UpdateUser(ctx context.Context, in *pb.User) (*pb.User, error) {
log.Printf("update user info called")
updatedUser := model.User{
Password: in.GetPassword(),
Email: in.GetEmail(),
PhoneNumber: in.GetPhoneNumber(),
}
updatedUser.ID = uint(in.Id)
err := s.Db.UpdateUser(updatedUser)
utils.CheckCall(err)
if err != nil {
return nil, err
}
return &pb.User{Password: updatedUser.Password, Email: updatedUser.Email, PhoneNumber: updatedUser.PhoneNumber}, nil
}
// InitiateGoogleSignIn initiates the Google OAuth2 sign-in process
func (s *BmsServer) InitiateGoogleSignIn(w http.ResponseWriter, r *http.Request) {
url := googleOauthConfig.AuthCodeURL("state-token", oauth2.AccessTypeOffline)
http.Redirect(w, r, url, http.StatusTemporaryRedirect)
}
// HandleGoogleCallback handles the OAuth2 callback from Google
func (s *BmsServer) HandleGoogleCallback(w http.ResponseWriter, r *http.Request) {
code := r.URL.Query().Get("code")
token, err := googleOauthConfig.Exchange(context.Background(), code)
if err != nil {
http.Error(w, "Failed to exchange token: "+err.Error(), http.StatusInternalServerError)
return
}
client := googleOauthConfig.Client(context.Background(), token)
resp, err := client.Get("https://www.googleapis.com/oauth2/v2/userinfo")
if err != nil {
http.Error(w, "Failed to get user info: "+err.Error(), http.StatusInternalServerError)
return
}
defer resp.Body.Close()
var userInfo struct {
ID string `json:"id"`
Email string `json:"email"`
VerifiedEmail bool `json:"verified_email"`
Picture string `json:"picture"`
}
if err := json.NewDecoder(resp.Body).Decode(&userInfo); err != nil {
http.Error(w, "Failed to decode user info: "+err.Error(), http.StatusInternalServerError)
return
}
// Here you would typically create or update the user in your database
// For example:
// user := model.User{
// GoogleID: userInfo.ID,
// Email: userInfo.Email,
// AccessToken: token.AccessToken,
// RefreshToken: token.RefreshToken,
// }
// s.Db.CreateOrUpdateUser(user)
// For now, just return the user info as JSON
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(userInfo)
}
OAuth2 Configuration:
googleOauthConfig
is configured with the client ID, client secret, redirect URL, and scopes required for Google OAuth2.InitiateGoogleSignIn:
HandleGoogleCallback:
This implementation provides the necessary endpoints to initiate Google sign-in and handle the OAuth2 callback, integrating Google OAuth2 authentication into your application.
Click here to create a Pull Request with the proposed solution
Files used for this task:
What - description of what you me to do Example: Hey @autopilot implement a Google sign-in on my website. Make changes to the front end and the back end of the application
Why - explain why this is important Example: I want to allow users to signup and login using their Google account