Skip to content

Commit

Permalink
Merge pull request #9 from gdsc-ncku/Feat/line-login
Browse files Browse the repository at this point in the history
line login
  • Loading branch information
peterxcli authored Jan 13, 2024
2 parents b0ee30d + b798c7f commit 4e6b8b7
Show file tree
Hide file tree
Showing 10 changed files with 171 additions and 11 deletions.
5 changes: 4 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ APP_DB_HOST=localhost
APP_DB_PORT=5432
APP_DB_USERNAME=test_user
APP_DB_PASSWORD=test_password
APP_DB_DATABASE=testdb
APP_DB_DATABASE=postgres

APP_REDIS_HOST=localhost
APP_REDIS_PORT=6379
Expand All @@ -16,6 +16,9 @@ APP_JWT_REFRESH_SECRET=secret
APP_JWT_ACCESS_EXPIRY=3600
APP_JWT_REFRESH_EXPIRY=86400

APP_LINE_CHANNEL_ID=secret
APP_LINE_CHANNEL_SECRET=secret

APP_DOMAIN=:80

APP_PGADMIN_EMAIL=[email protected]
Expand Down
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@

### Official Document

- [line messaging github](https://github.com/line/line-bot-sdk-go)
- [integrate line login](https://developers.line.biz/en/docs/line-login/integrate-line-login/)

### Tutorial

#### 1. [[Golang][LINE][教學] 導入 LINE Login 到你的商業網站之中,並且加入官方帳號為好友](https://www.evanlin.com/line-login/)
#### 1. Line Login Integration Tutorial

- [github](https://github.com/line/line-bot-sdk-go)
- [[Golang][LINE][教學] 將你的 chatbot 透過 account link 連接你的服務](https://www.evanlin.com/line-accountlink/)
- [[Golang][LINE][教學] 導入 LINE Login 到你的商業網站之中,並且加入官方帳號為好友](https://www.evanlin.com/line-login/)
- [github](https://github.com/kkdai/line-login-go)

## Branch/Commit Type

Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ require (
github.com/jinzhu/now v1.1.5 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kkdai/line-login-sdk-go v0.6.1 // indirect
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/magiconair/properties v1.8.7 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kkdai/line-login-sdk-go v0.6.1 h1:YL4iPisaz6iI16BPhNNeW/MdIfLPDVa9JPmT9lDom8U=
github.com/kkdai/line-login-sdk-go v0.6.1/go.mod h1:0D2lfcUZ5YHDdPraslN8RL3Gb3sZHbJlDbmWCL74xFU=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
Expand Down
20 changes: 12 additions & 8 deletions pkg/bootstrap/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package bootstrap
import (
"context"
"fmt"
social "github.com/kkdai/line-login-sdk-go"
"log"
"net/http"
"os"
Expand All @@ -19,17 +20,19 @@ import (
type AppOpts func(app *Application)

type Application struct {
Env *Env
Conn *gorm.DB
Cache *redis.Client
Engine *gin.Engine
Env *Env
Conn *gorm.DB
Cache *redis.Client
Engine *gin.Engine
LineSocialClient *social.Client
}

func App(opts ...AppOpts) *Application {
env := NewEnv()
db := NewDB(env)
cache := NewCache(env)
engine := gin.Default()
lineSocialClient := NewLineSocialClient(env)

// Set timezone
tz, err := time.LoadLocation(env.Server.TimeZone)
Expand All @@ -39,10 +42,11 @@ func App(opts ...AppOpts) *Application {
time.Local = tz

app := &Application{
Env: env,
Conn: db,
Cache: cache,
Engine: engine,
Env: env,
Conn: db,
Cache: cache,
Engine: engine,
LineSocialClient: lineSocialClient,
}

for _, opt := range opts {
Expand Down
1 change: 1 addition & 0 deletions pkg/bootstrap/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ type Env struct {
Redis RedisEnv `envPrefix:"REDIS_"`
Server Server `envPrefix:"SERVER_"`
JWT JWTEnv `envPrefix:"JWT_"`
Line LineEnv `envPrefix:"LINE_"`
Domain string `env:"DOMAIN"`
}

Expand Down
13 changes: 13 additions & 0 deletions pkg/bootstrap/line.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package bootstrap

import social "github.com/kkdai/line-login-sdk-go"

type LineEnv struct {
ChannelID string `env:"CHANNEL_ID"`
ChannelSecret string `env:"CHANNEL_SECRET"`
}

func NewLineSocialClient(env *Env) *social.Client {
client, _ := social.New(env.Line.ChannelID, env.Line.ChannelSecret)
return client
}
117 changes: 117 additions & 0 deletions pkg/controller/oauth_controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package controller

import (
"bikefest/pkg/bootstrap"
"bikefest/pkg/model"
"fmt"
"github.com/gin-gonic/gin"
social "github.com/kkdai/line-login-sdk-go"
"log"
"net/http"
"strconv"
)

func NewOAuthController(lineSocialClient *social.Client, env *bootstrap.Env, userSvc model.UserService) *OAuthController {
return &OAuthController{
lineSocialClient: lineSocialClient,
userSvc: userSvc,
env: env,
}
}

type OAuthController struct {
lineSocialClient *social.Client
userSvc model.UserService
env *bootstrap.Env
}

// http://localhost:8000/line-login/auth
func (ctrl *OAuthController) LineLogin(c *gin.Context) {
//TODO: place `serverURL` into env
serverURL := "http://localhost:8000"
scope := "profile openid" //profile | openid | email
state := social.GenerateNonce()
nonce := social.GenerateNonce()
redirectURL := fmt.Sprintf("%s/line-login/callback", serverURL)
targetURL := ctrl.lineSocialClient.GetWebLoinURL(redirectURL, state, scope, social.AuthRequestOptions{Nonce: nonce, Prompt: "consent"})
c.Redirect(http.StatusMovedPermanently, targetURL)
}

func (ctrl *OAuthController) LineLoginCallback(c *gin.Context) {
//TODO: place `serverURL` and `frontendURL` into env
serverURL := "http://localhost:8000"
frontendURL := "http://localhost:3000"
code := c.Query("code")
_ = c.Query("state")
token, err := ctrl.lineSocialClient.GetAccessToken(fmt.Sprintf("%s/line-login/callback", serverURL), code).Do()
if err != nil {
log.Println("RequestLoginToken err:", err)
return
}
log.Println("access_token:", token.AccessToken, " refresh_token:", token.RefreshToken)

var payload *social.Payload
//if len(token.IDToken) == 0 {
// // User don't request openID, use access token to get user profile
// log.Println(" token:", token, " AccessToken:", token.AccessToken)
// res, err := ctrl.lineSocialClient.GetUserProfile(token.AccessToken).Do()
// if err != nil {
// log.Println("GetUserProfile err:", err)
// return
// }
// payload = &social.Payload{
// Name: res.DisplayName,
// Picture: res.PictureURL,
// }
//} else {
//Decode token.IDToken to payload
payload, err = token.DecodePayload(ctrl.env.Line.ChannelID)
if err != nil {
log.Println("DecodeIDToken err:", err)
return
}
//}
log.Printf("payload: %#v", payload)

//c.JSON(http.StatusOK, gin.H{
// "status": "Success",
// "data": payload,
//})

user := &model.User{
ID: payload.Sub,
Name: payload.Name,
}

err = ctrl.userSvc.CreateFakeUser(c, user)

if err != nil {
log.Printf("user with id %s already exists", user.ID)
}

accessToken, err := ctrl.userSvc.CreateAccessToken(c, user, ctrl.env.JWT.AccessTokenSecret, ctrl.env.JWT.AccessTokenExpiry)
if err != nil {
log.Printf("failed to create access token: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{
"status": "Failed",
"message": "failed to create access token",
})
return
}

refreshToken, err := ctrl.userSvc.CreateRefreshToken(c, user, ctrl.env.JWT.RefreshTokenSecret, ctrl.env.JWT.RefreshTokenExpiry)
if err != nil {
log.Printf("failed to create refresh token: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{
"status": "Failed",
"message": "failed to create refresh token",
})
return
}

// set to cookie
c.SetCookie("access_token", strconv.FormatInt(ctrl.env.JWT.AccessTokenExpiry, 10), 3600, "/", "", false, true)
c.SetCookie("refresh_token", strconv.FormatInt(ctrl.env.JWT.AccessTokenExpiry, 10), 3600, "/", "", false, true)
// redirect to frontend
c.Redirect(http.StatusMovedPermanently, fmt.Sprintf("%s/oauth?access_token=%s&refresh_token=%s", frontendURL, accessToken, refreshToken))
}
12 changes: 12 additions & 0 deletions pkg/router/oauth_route.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package router

import (
"bikefest/pkg/bootstrap"
"bikefest/pkg/controller"
)

func RegisterOAuthRouter(app *bootstrap.Application, controller *controller.OAuthController) {
lineRouter := app.Engine.Group("/line-login")
lineRouter.GET("/auth", controller.LineLogin)
lineRouter.GET("/callback", controller.LineLoginCallback)
}
4 changes: 4 additions & 0 deletions pkg/router/route.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,8 @@ func RegisterRoutes(app *bootstrap.Application, services *Services) {
// Register PsychoTest Routes
psychoTestController := controller.NewPsychoTestController(app.Conn)
RegisterPsychoTestRouter(app, psychoTestController)

// Register OAuth Routes
oauthController := controller.NewOAuthController(app.LineSocialClient, app.Env, services.UserService)
RegisterOAuthRouter(app, oauthController)
}

0 comments on commit 4e6b8b7

Please sign in to comment.