Skip to content

Commit

Permalink
Merge pull request #16 from Extrawurst/master
Browse files Browse the repository at this point in the history
add linkedin example and allow token_type to be empty
  • Loading branch information
thaven authored May 2, 2018
2 parents 70f1b94 + f5b539a commit 5da60a5
Show file tree
Hide file tree
Showing 9 changed files with 224 additions and 1 deletion.
2 changes: 2 additions & 0 deletions examples/linkedin-login/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
linkedin.json
linkedin-login-example
12 changes: 12 additions & 0 deletions examples/linkedin-login/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# LinkedIn Connect Example

If you want to try running this example, please register as a [LinkedIn developer](https://developer.linkedin.com/) and create an App. Then, create a file named `linkedin.json` with contents like this:

```json
{
"clientId": "YOUR_APP_ID_HERE",
"clientSecret": "YOUR_APP_SECRET_HERE",
"redirectUri": "http://localhost:8080/login/linkedin"
}
```

15 changes: 15 additions & 0 deletions examples/linkedin-login/dub.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "linkedin-login-example",
"dependencies": {
"oauth": {
"path": "..\/..\/"
},
"vibe-d:http": "*",
"vibe-d:core": "*",
"vibe-d:data": "*",
"vibe-d:web": "*"
},
"versions": [
"VibeDefaultMain"
]
}
92 changes: 92 additions & 0 deletions examples/linkedin-login/source/app.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import vibe.http.server;
import vibe.web.auth;
import vibe.web.web;

import oauth.webapp;

///
struct AuthInfo
{
}

///
@requiresAuth class LinkedInLoginExample : OAuthWebapp
{
private
{
import oauth.settings : OAuthSettings;

static immutable OAuthSettings _oauthSettings;
static immutable _scopes = ["r_basicprofile"];
}

shared static this()
{
import vibe.core.file : readFileUTF8;
import vibe.data.json : parseJsonString;
import oauth.provider.linkedin : LinkedInAuthSettings;

_oauthSettings = new immutable(LinkedInAuthSettings)(
"linkedin.json".readFileUTF8.parseJsonString());
}

@noRoute @safe AuthInfo authenticate(scope HTTPServerRequest req, scope HTTPServerResponse res)
{
if (!isLoggedIn(req, _oauthSettings))
login(req, res, _oauthSettings, _scopes);

return AuthInfo();
}

@path("/")
@anyAuth void getIndex(scope HTTPServerRequest req)
{
import vibe.http.client : requestHTTP;
import vibe.data.json : Json;

auto session = this.oauthSession(req);
assert(session, "No session: authenticate() not called??");

Json userInfo;

requestHTTP("https://api.linkedin.com/v1/people/~?format=json", delegate(scope graphReq) {
session.authorizeRequest(graphReq);
}, delegate(scope graphRes) {
auto obj = graphRes.readJson();
if ("error" !in obj)
userInfo = obj;
});

render!("index.dt", userInfo);
}

@path("/login/linkedin")
@noAuth void getLoginLinkedIn(scope HTTPServerRequest req, scope HTTPServerResponse res)
{
login(req, res, _oauthSettings, _scopes);

if (!res.headerWritten)
res.redirect("/");
}

@anyAuth void getLogout()
{
terminateSession();
render!("logout.dt");
}
}

shared static this()
{
import vibe.http.router : URLRouter;
import vibe.http.session : MemorySessionStore;

auto router = new URLRouter;
router.registerWebInterface(new LinkedInLoginExample);

auto settings = new HTTPServerSettings;
settings.sessionStore = new MemorySessionStore;
settings.port = 8080;

listenHTTP(settings, router);
}
9 changes: 9 additions & 0 deletions examples/linkedin-login/views/index.dt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
extends layout

block body
h1 Welcome #{ userInfo["firstName"].get!string }
p You've been logged in successfully!
p LinkedIn told me your name is #{ userInfo["firstName"].get!string }
| #{ userInfo["lastName"].get!string } and your headline is
| #{ userInfo["headline"].get!string }.

7 changes: 7 additions & 0 deletions examples/linkedin-login/views/layout.dt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
doctype html
html
head
title LinkedIn authentication example
body
block body

6 changes: 6 additions & 0 deletions examples/linkedin-login/views/logout.dt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
extends layout

block body
h1 Logout
p You've been logged out successfully!

78 changes: 78 additions & 0 deletions source/oauth/provider/linkedin.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
module oauth.provider.linkedin;

import oauth.provider : OAuthProvider;
import oauth.settings : OAuthSettings;

import vibe.data.json;
import vibe.http.client;
import vibe.inet.webform;

import std.exception;

/++
Settings for 'linkedin' provider.
OAuthSettings specialized for Linkedin. Just for convenience.
+/
class LinkedInAuthSettings : OAuthSettings
{
private bool _rerequest;

/++
See OAuthSettings constructor for common documentation.
The 'provider' field may be omitted. If it is included, it MUST be set
to "linkedin".
+/
this(in Json config) immutable
{
enforce("provider" !in config || (config["provider"].type == Json.Type.string
&& config["provider"].get!string == "linkedin"),
"LinkedInAuthSettings can only be used with provider 'linkedin'.");

super("linkedin", config["clientId"].get!string,
config["clientSecret"].get!string, config["redirectUri"].get!string);

if (config["authType"].type == Json.Type.string
&& config["authType"].get!string == "rerequest")
_rerequest = true;
}
}

/++
LinkedIn specialized derivative of OAuthProvider.
This class should not be used directly. It registers itself as an
OAuthProvider with name `linkedin`.
+/
class LinkedInAuthProvider : OAuthProvider
{
shared static this()
{
OAuthProvider.register("linkedin", new immutable(LinkedInAuthProvider));
}

private this() immutable
{
import std.typecons : BitFlags;

super("https://www.linkedin.com/oauth/v2/authorization", "https://www.linkedin.com/oauth/v2/accessToken",
BitFlags!Option(Option.explicitRedirectUri,
Option.tokenRequestHttpGet, Option.clientAuthParams));
}

override void authUriHandler(immutable OAuthSettings settings, string[string] params) const
{
if (auto liSettings = cast(immutable LinkedInAuthSettings) settings)
if ("auth_type" !in params && liSettings._rerequest)
params["auth_type"] = "rerequest";
}
}

unittest
{
auto linkedin = OAuthProvider.forName("linkedin");
assert(linkedin, "Name 'linkedin' not registered!");
assert(cast(LinkedInAuthProvider) linkedin,
"Some unkown OAuthProvider has been registered as 'linkedin'.");
}
4 changes: 3 additions & 1 deletion source/oauth/session.d
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,9 @@ class OAuthSession
_tokenData = atr;
_timestamp = timestamp;

enforce(this.tokenType == "bearer", new OAuthException(
immutable tkType = this.tokenType;

enforce(tkType == "bearer" || tkType == "", new OAuthException(
format("Unsupported token type: %s", this.tokenType)));

enforce!OAuthException(this.token, "No token received.");
Expand Down

0 comments on commit 5da60a5

Please sign in to comment.