Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/google-github-login/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
google-github-login-example
13 changes: 13 additions & 0 deletions examples/google-github-login/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Google-Github Example

If you want to try running this example, please register as a Github and Google developer and create apps. Be sure to use the correct redirect URIs (check `oauthimpl.d`).
Lastly, you need to export the environment credentials to the app as follows:

```sh
export GITHUB_OAUTH_CLIENTID="..."
export GITHUB_OAUTH_CLIENTSECRET="..."

export GOOGLE_OAUTH_CLIENTID="foo.apps.googleusercontent.com"
export GOOGLE_OAUTH_CLIENTSECRET="..."
export GOOGLE_OAUTH_PROJECTID="project123..."
```
16 changes: 16 additions & 0 deletions examples/google-github-login/dub.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name" : "google-github-login-example",
"dependencies" : {
"oauth" : {
"path" : "..\/..\/"
},
"vibe-d:http" : "*",
"vibe-d:core" : "*",
"vibe-d:data" : "*",
"vibe-d:web" : "*",
"vibe-d:mongodb" : "*"
},
"versions" : [
"VibeDefaultMain"
]
}
57 changes: 57 additions & 0 deletions examples/google-github-login/source/app.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import vibe.http.router : URLRouter;
import vibe.http.server : HTTPServerSettings, listenHTTP, render;
import vibe.data.json : Json;
import vibe.http.session : MemorySessionStore;

shared static this()
{
import vibe.core.log;
setLogLevel(LogLevel.debug_);

auto router = new URLRouter;

router.get("/api/user/logout", (scope req, scope res) {
res.terminateSession();
logDebug("user logged out");
res.redirect("/");
});

import oauthimpl : registerOAuth, isLoggedIn;
router.registerOAuth;

// example route to dump the current session
with(router) {
get("/api/session", (req, res) {
if (req.session && req.session.isKeySet("user"))
res.writeJsonBody(req.session.get!Json("user"));
else
res.writeBody("Empty Session");
});
}

// load and init a permanent user storage
import std.process : environment;
import vibe.db.mongo.mongo : connectMongoDB;
import users : UserController, users, User;
auto host = environment.get("APP_MONGO_URL", "mongodb://localhost");
auto dbName = environment.get("APP_MONGO_DB", "hackback");
auto db = connectMongoDB(host).getDatabase(dbName);
users = new UserController(db);

import std.typecons : Nullable;
// A simple main page
with(router) {
get("/", (req, res) {
Nullable!User user;
if (req.session && req.session.isKeySet("user"))
user = req.session.get!User("user");

res.render!("index.dt", user);
});
}

auto settings = new HTTPServerSettings;
settings.port = 8080;
settings.sessionStore = new MemorySessionStore;
listenHTTP(settings, router);
}
151 changes: 151 additions & 0 deletions examples/google-github-login/source/oauthimpl.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import oauth.settings : OAuthSettings;
import oauth.webapp : OAuthWebapp;

import vibe.http.router : URLRouter;
import vibe.http.client : requestHTTP;
import vibe.http.server : HTTPServerRequest, HTTPServerResponse;
import vibe.data.json : Json;

OAuthWebapp webapp;
immutable(OAuthSettings) googleOAuthSettings;
immutable(OAuthSettings) githubOAuthSettings;
string finalRedirectUri;

/++
Load OAuth configuration from environment variables.
+/
auto loadFromEnvironment(
string providerName,
string envPrefix,
string redirectUri,
)
{
import std.process : environment;
string clientId = environment[envPrefix ~ "_CLIENTID"];
string clientSecret = environment[envPrefix ~ "_CLIENTSECRET"];

return new immutable(OAuthSettings)(
providerName,
clientId,
clientSecret,
redirectUri);
}

shared static this()
{
import oauth.provider.github;
import oauth.provider.google;
import std.process : environment;

webapp = new OAuthWebapp;

// oauth stuff
// TODO: make callback uri configureable
googleOAuthSettings = loadFromEnvironment("google", "GOOGLE_OAUTH", "http://localhost:8080/api/user/login/google");
githubOAuthSettings = loadFromEnvironment("github", "GITHUB_OAUTH", "http://localhost:8080/api/user/login/github");
finalRedirectUri = "/";
}

string[] googleScopes = ["https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/userinfo.profile"];

string[] githubScopes = ["user:email"];

import users : User, users;

bool isLoggedIn(scope HTTPServerRequest req) @safe {
if (!req.session)
return false;

if (req.session.isKeySet("user"))
return true;

return false;
}

void registerOAuth(scope URLRouter router)
{
router.get("/api/user/login/error", (req, res) {
res.writeBody("An error happened");
});

router.get("/api/user/login/google", (req, res) @safe {
// TODO: necessary?
if (isLoggedIn(req))
{
return res.redirect(finalRedirectUri);
}
else if (webapp.login(req, res, googleOAuthSettings, null, googleScopes))
{
// TODO: oauth.session is fetched from session store (was set in webapp.login)
auto session = webapp.oauthSession(req, googleOAuthSettings);
requestHTTP(
"https://www.googleapis.com/userinfo/v2/me",
(scope googleReq) { session.authorizeRequest(googleReq); },
(scope googleRes) {
auto userInfo = googleRes.readJson();
if ("error" !in userInfo)
{
User user = {
email: userInfo["email"].get!string,
name: userInfo["name"].get!string,
avatarUrl: userInfo["picture"].get!string,
googleId: userInfo["id"].get!string
};
user = users.loginOrSignup!"googleId"(user);
req.session.set("user", user);
assert(isLoggedIn(req));
return res.redirect(finalRedirectUri);
}
res.redirect("/api/user/login/error");
});
}
});

router.get("/api/user/login/github", (req, res) {
// TODO: necessary?
if (isLoggedIn(req))
{
return res.redirect(finalRedirectUri);
}
else if (webapp.login(req, res, githubOAuthSettings, null, githubScopes))
{
// TODO: oauth.session is fetched from session store (was set in webapp.login)
auto session = webapp.oauthSession(req, githubOAuthSettings);
requestHTTP(
"https://api.github.com/user",
delegate (scope githubReq) {
githubReq.headers["Accept"] = "application/vnd.github.v3+json";
session.authorizeRequest(githubReq);
},
delegate (scope githubRes) {
auto userInfo = githubRes.readJson();

// TODO: join requests!
requestHTTP(
"https://api.github.com/user/emails",
delegate (scope githubReq) {
githubReq.headers["Accept"] = "application/vnd.github.v3+json";
session.authorizeRequest(githubReq); },
delegate (scope emailRes) {
auto userEmail = emailRes.readJson();

import vibe.http.common : enforceBadRequest;
enforceBadRequest(userEmail.length >= 1, "At least one email expected");

User user = {
name: userInfo["name"].get!string,
email: userEmail[0]["email"].get!string,
avatarUrl: userInfo["avatar_url"].get!string,
githubId: userInfo["id"].get!long,
};
user = users.loginOrSignup!"githubId"(user);
req.session.set("user", user);

assert(isLoggedIn(req));
res.redirect(finalRedirectUri);
});
});
}
});
}
56 changes: 56 additions & 0 deletions examples/google-github-login/source/users.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import vibe.data.bson : BsonObjectID;
import vibe.db.mongo.mongo;
import std.typecons : tuple;

struct User
{
import vibe.data.serialization : dbName = name;
@dbName("_id") BsonObjectID id;
string email;
string name;
long githubId;
string googleId;
string avatarUrl;
}

// TLS instance
UserController users;

class UserController
{
MongoCollection m_users;

this(MongoDatabase db)
{
m_users = db["users"];

m_users.ensureIndex([tuple("googleId", 1)], IndexFlags.unique | IndexFlags.sparse);
m_users.ensureIndex([tuple("githubId", 1)], IndexFlags.unique | IndexFlags.sparse);
}

User loginOrSignup(string providerId)(User user)
{
auto u = m_users.findOne!User([providerId: mixin("user." ~ providerId)]);
if (!u.isNull)
{
// TODO: should we update attributes?
return u.get;
}
else
{
return addUser(user);
}
}

User addUser(User user)
{
user.id = BsonObjectID.generate();
m_users.insert(user);
return user;
}

void updateToken(string id, string token)
{
m_users.update(["id": id], ["$set": token]);
}
}
13 changes: 13 additions & 0 deletions examples/google-github-login/views/index.dt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
- import vibe.data.json : serializeToPrettyJson;
doctype html
html
head
title Google-Github authentication example
body
- if (user.isNull)
a(href="/api/user/login/github") Login with GitHub
br
a(href="/api/user/login/google") Login with Google
- else
a(href="/api/user/logout") Logout
div #{ user.get.serializeToPrettyJson}