Skip to content

Commit

Permalink
Refactor profile and share functions
Browse files Browse the repository at this point in the history
  • Loading branch information
mehmet.solak committed Sep 8, 2024
1 parent a652101 commit 4dc9429
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 276 deletions.
196 changes: 86 additions & 110 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,164 +4,140 @@ import (
"crypto/rand"
"encoding/json"
"fmt"
"io/ioutil"
"io"
"net/http"
"strings"
"time"

"github.com/gofiber/fiber/v2"
)

type Authentication interface {
Authorize() string
RedirectToURL(*fiber.Ctx) error
RetrieveAccessToken(*fiber.Ctx) error
Profile(*fiber.Ctx) error
SharePost(*fiber.Ctx) error
ShareJOBPosting(*fiber.Ctx) error
}

type Linkedin struct {
// Redirect to linkedin Auth screen with this url.
// Must be contain ClientID, RedirectURI, ClientSecret, Scopes and State.
AuthURL string `json:"authURL"`

// Set 'code' by default.
ResponseType string `json:"responseType"`

// Your linkedin app's ClientID.
// You can take your ClientID after create linkedin app.
ClientID string `json:"client_id"`
type Config struct {
// The response type, set to 'code' by default, which is the standard for authorization flows.
ResponseType string

// Your linkedin app's ClientSecret.
// You can take your ClientSecret after create linkedin app.
ClientSecret string `json:"client_secret"`
// Your LinkedIn app's ClientID, which is obtained after creating a LinkedIn app.
ClientID string

// Your callback url.
// You can define this while you creating your linkedin app.
RedirectURI string `json:"redirect_uri"`
// Your LinkedIn app's ClientSecret, obtained after creating the LinkedIn app.
ClientSecret string

// Your State token, creating random.
State string `json:"state"`
// The callback URL you define when setting up your LinkedIn app. It will be used for redirecting users.
RedirectURI string

// Our permissions.
// Set 'r_liteprofile,r_emailaddress,w_member_social,w_share' by defualt.
Scope string `json:"scope"`

// You can take AccessToken when you redirect to callback url.
// This defined in your redirect URL as 'code'.
AccessToken string `json:"access_token"`
// The requested permissions (scopes). Set to 'r_liteprofile', 'r_emailaddress', 'w_member_social', and 'w_share' by default.
Scopes []string
}

// ProfileInformation inherit on API Struct.
ProfileInformation ProfileInformation `json:"profile_information"`
type Linkedin struct {
// The configuration for the LinkedIn OAuth client, using the `Config` struct.
config Config
}

var (
AuthURL = "https://www.linkedin.com/oauth/v2/authorization?"
// Base URL for LinkedIn's OAuth authentication endpoint.
AuthenticationBaseURL = "https://www.linkedin.com/oauth/v2/authorization?"

// Base URL for requesting the access token after obtaining the authorization code.
AccessTokenBaseURL = "https://www.linkedin.com/uas/oauth2/accessToken?grant_type=authorization_code"
)

/*
New function create a new linkedin API struct.
Take arguments, Client ID, Redirect URL and
Client Secret. Thats arguments can find on
linkedin portal. Also, send Scopes argument for
give permission your api.
New creates a new LinkedIn API client with the given configuration.
This requires the Client ID, Redirect URL, and Client Secret, which can be found
on the LinkedIn Developer portal. Optionally, permissions (scopes) can be provided.
If no config is provided, an error is returned.
*/
func New(clientId, redirectUrl, clientSecret string, scopes []string) (*Linkedin, error) {
if len(scopes) == 0 {
return nil, fmt.Errorf("scopes must take valid value")
func New(config ...Config) (*Linkedin, error) {
linkedin := &Linkedin{
config: Config{},
}

for _, scp := range scopes {
switch scp {
case "r_liteprofile", "r_emailaddress", "w_member_social":
continue
default:
return nil, fmt.Errorf("invalid scope")
}
if len(config) == 0 {
return nil, fmt.Errorf("%s", "please provide a config!")
}

api := Linkedin{
ClientID: clientId,
ClientSecret: clientSecret,
RedirectURI: redirectUrl,
ResponseType: "code",
Scope: strings.Join(scopes, ","),
State: tokenGenerator(),
linkedin.config = config[0]

return linkedin, nil
}

/*
setAuthURL builds the LinkedIn authorization URL for the client.
It generates a random state for security and joins the required scopes.
The resulting URL is used to redirect the user for authentication.
*/
func (config *Config) setAuthURL() string {
if len(config.Scopes) == 0 {
config.Scopes = []string{"r_liteprofile", "r_emailaddress", "w_member_social", "w_share"}
}

api.AuthURL = fmt.Sprintf("%sresponse_type=%s&client_id=%s&redirect_uri=%s&state=%s&scope=%s&client_secret=%s", AuthURL, api.ResponseType, api.ClientID, api.RedirectURI, api.State, api.Scope, api.ClientSecret)
state := stateGenerator()
scopes := strings.Join(config.Scopes, ",")

return &api, nil
return AuthenticationBaseURL +
"response_type=" + config.ResponseType +
"&client_id=" + config.ClientID +
"&redirect_uri=" + config.RedirectURI +
"&state=" + state +
"&scope=" + scopes +
"&client_secret=" + config.ClientSecret
}

/*
Redirect URL will be redirect you to AuthURL.
setAccessTokenURL generates the access token request URL, which is used to exchange
the authorization code for an access token. It includes the redirect URI, client ID, and client secret.
*/
func (ln *Linkedin) RedirectToURL(c *fiber.Ctx) error {
return c.Redirect(ln.AuthURL, http.StatusFound)
func (cfg *Config) setAccessTokenURL(code string) string {
return AccessTokenBaseURL + code +
"&redirect_uri=" + cfg.RedirectURI +
"&client_id=" + cfg.ClientID +
"&client_secret=" + cfg.ClientSecret
}

/*
After redirecting to auth url, you must to callback here.
When you set something on linkedin portal you must to be
determine call back url, and this url point to RetrieveAccessToken's
endpoint.
GetAuthenticationUrl returns the LinkedIn authorization URL, allowing the client to
authenticate users and receive an authorization code.
*/
func (ln *Linkedin) RetrieveAccessToken(c *fiber.Ctx) error {
client := http.Client{}
func (ln *Linkedin) GetAuthenticationUrl() string {
return ln.config.setAuthURL()
}

queryToken := c.Query("code")
/*
RetrieveAccessToken exchanges the authorization code for an access token by making an
HTTP request to LinkedIn's OAuth API. The access token is required for accessing LinkedIn's API.
*/
func (ln *Linkedin) RetrieveAccessToken(code string) (string, error) {
type response struct {
AccessToken string `json:"access_token"`
}

accessTokenURL := "https://www.linkedin.com/uas/oauth2/accessToken?grant_type=authorization_code&code=" + queryToken + "&redirect_uri=" + ln.RedirectURI + "&client_id=" + ln.ClientID + "&client_secret=" + ln.ClientSecret
client := http.Client{}

req, err := http.NewRequest(http.MethodGet, accessTokenURL, nil)
if err != nil {
c.Status(400)
return c.JSON(fiber.Map{"error": "get access token error"})
}
accessTokenURL := ln.config.setAccessTokenURL(code)

req, _ := http.NewRequest(http.MethodGet, accessTokenURL, nil)

resp, err := client.Do(req)
if err != nil {
c.Status(400)
return c.JSON(fiber.Map{"error": "client do error"})
return "", fmt.Errorf("http request error: %s", err.Error())
}

data, err := ioutil.ReadAll(resp.Body)
if err != nil {
c.Status(400)
return c.JSON(fiber.Map{"error": "body read error"})
}
resp.Body.Close()
defer resp.Body.Close()

var responseBody map[string]interface{}
err = json.Unmarshal(data, &responseBody)
data, err := io.ReadAll(resp.Body)
if err != nil {
c.Status(400)
return c.JSON(fiber.Map{"error": "unmarshal error"})
return "", fmt.Errorf("read body error: %s", err.Error())
}

if _, err := responseBody["error"]; err {
c.Status(400)
return c.JSON(fiber.Map{"error": responseBody["error"].(string)})
}

ln.AccessToken = responseBody["access_token"].(string)

c.Cookie(&fiber.Cookie{
Name: "linkedin_token",
Value: ln.AccessToken,
HTTPOnly: true,
Expires: time.Now().Add(time.Hour * 24),
})
var r response
_ = json.Unmarshal(data, &r)

return c.Status(200).JSON(fiber.Map{"access_token": ln.AccessToken})
return r.AccessToken, nil
}

/*
tokenGenerator() function creating TOKEN for state query.
stateGenerator generates a random state string for OAuth2 requests.
This state is used to prevent CSRF attacks during the authentication process.
*/
func tokenGenerator() string {
func stateGenerator() string {
b := make([]byte, 20)
rand.Read(b)
return fmt.Sprintf("%x", b)
Expand Down
16 changes: 0 additions & 16 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,19 +1,3 @@
module github.com/knetic0/golinkedin

go 1.19

require github.com/gofiber/fiber/v2 v2.50.0

require (
github.com/andybalholm/brotli v1.0.5 // indirect
github.com/google/uuid v1.3.1 // indirect
github.com/klauspost/compress v1.16.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.50.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect
golang.org/x/sys v0.13.0 // indirect
)
27 changes: 0 additions & 27 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,27 +0,0 @@
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/gofiber/fiber/v2 v2.50.0 h1:ia0JaB+uw3GpNSCR5nvC5dsaxXjRU5OEu36aytx+zGw=
github.com/gofiber/fiber/v2 v2.50.0/go.mod h1:21eytvay9Is7S6z+OgPi7c7n4++tnClWmhpimVHMimw=
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.50.0 h1:H7fweIlBm0rXLs2q0XbalvJ6r0CUPFWK3/bB4N13e9M=
github.com/valyala/fasthttp v1.50.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA=
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
38 changes: 14 additions & 24 deletions profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@ package golinkedin
import (
"encoding/json"
"fmt"
"io/ioutil"
"io"
"net/http"

"github.com/gofiber/fiber/v2"
)

type ProfileInformation struct {
Expand All @@ -20,23 +18,20 @@ type ProfileInformation struct {
LastName string `json:"localizedLastName"`
}

var (
ProfileURL = "https://api.linkedin.com/v2/me"
)

/*
After than Callback, you take Profile information with this function.
Create route for this.
*/
func (ln *Linkedin) Profile(c *fiber.Ctx) error {
profile_url := "https://api.linkedin.com/v2/me"

token := c.Cookies("linkedin_token")

func (ln *Linkedin) Profile(token string) (*ProfileInformation, error) {
authorization := fmt.Sprintf("Bearer %s", token)

client := http.Client{}

req, err := http.NewRequest("GET", profile_url, nil)
if err != nil {
return c.Status(400).JSON(fiber.Map{"error": "new request created unsuccess"})
}
req, _ := http.NewRequest("GET", ProfileURL, nil)

req.Header = http.Header{
"Content-Type": {"application/json"},
Expand All @@ -45,22 +40,17 @@ func (ln *Linkedin) Profile(c *fiber.Ctx) error {

res, err := client.Do(req)
if err != nil {
c.Status(400)
return c.JSON(fiber.Map{"error": "request send error"})
return nil, fmt.Errorf("http request error: %s", err.Error())
}

body, err := ioutil.ReadAll(res.Body)
body, err := io.ReadAll(res.Body)
if err != nil {
c.Status(400)
return c.JSON(fiber.Map{"error": "readall error on res.Body"})
return nil, fmt.Errorf("read body error: %s", err.Error())
}

err = json.Unmarshal(body, &ln.ProfileInformation)
if err != nil {
c.Status(400)
return c.JSON(fiber.Map{"error": "unmarshal error"})
}
var profile ProfileInformation

_ = json.Unmarshal(body, &profile)

c.Status(200)
return c.JSON(fiber.Map{"profile": ln.ProfileInformation})
return &profile, nil
}
Loading

0 comments on commit 4dc9429

Please sign in to comment.