Authentification handlers.

For now, only Github OAuth is provided.

See https://developer.github.com/v3/oauth/.

This module provide 4 base classes that can be used together to implement a Github OAuth handshake.

Here an example of application using the Github Auth as login mechanism.

There is 4 available routes:

  • /login: redirects the user to the Github OAuth login page (see GithubLogin)
  • /profile: shows the currently logged in user (see Profile Handler)
  • /logout: logs out the user by destroying the entry from the session (see GithubLogout)
  • /oauth: callback url for Github service after player login (see GithubOAuthCallBack)

Routes redirection are handled at the OAuth service registration. Please see https://developer.github.com/v3/oauth/#redirect-urls for more niformation on how to configure your service to provide smouth redirections beween your routes.

import popcorn
import popcorn::pop_auth

class ProfileHandler
    super Handler

    redef fun get(req, res) do
        var session = req.session
        if session == null then
            res.send "No session :("
            return
        end
        var user = session.user
        if user == null then
            res.send "Not logged in"
            return
        end
        res.send "<h1>Hello {user.login}</h1>"
    end
end

var client_id = "github client id"
var client_secret = "github client secret"

var app = new App
app.use("/*", new SessionInit)
app.use("/login", new GithubLogin(client_id))
app.use("/oauth", new GithubOAuthCallBack(client_id, client_secret))
app.use("/logout", new GithubLogout)
app.use("/profile", new ProfileHandler)
app.listen("localhost", 3000)

Optionaly, you can use the GithubUser handler to provide access to the Github user stored in session:

app.use("/api/user", new GithubUser)

Introduced classes

abstract class AuthHandler

popcorn :: AuthHandler

AuthHandler allows access to session user
class GithubLogin

popcorn :: GithubLogin

Github OAuth login handler.
class GithubLogout

popcorn :: GithubLogout

Destroy user session and redirect to homepage.
class GithubOAuthCallBack

popcorn :: GithubOAuthCallBack

Get the authentification code and translate it to an access token.
class GithubUser

popcorn :: GithubUser

Get the currently logged in user from session.

Redefined classes

redef class Session

popcorn :: pop_auth $ Session

A server side session

All class definitions

abstract class AuthHandler

popcorn $ AuthHandler

AuthHandler allows access to session user
class GithubLogin

popcorn $ GithubLogin

Github OAuth login handler.
class GithubLogout

popcorn $ GithubLogout

Destroy user session and redirect to homepage.
class GithubOAuthCallBack

popcorn $ GithubOAuthCallBack

Get the authentification code and translate it to an access token.
class GithubUser

popcorn $ GithubUser

Get the currently logged in user from session.
redef class Session

popcorn :: pop_auth $ Session

A server side session
package_diagram popcorn::pop_auth pop_auth popcorn::pop_json pop_json popcorn::pop_auth->popcorn::pop_json popcorn::pop_sessions pop_sessions popcorn::pop_auth->popcorn::pop_sessions github github popcorn::pop_auth->github json json popcorn::pop_json->json popcorn::pop_handlers pop_handlers popcorn::pop_json->popcorn::pop_handlers popcorn::pop_validation pop_validation popcorn::pop_json->popcorn::pop_validation popcorn::pop_sessions->popcorn::pop_handlers github->json base64 base64 github->base64 curl curl github->curl nitcorn nitcorn github->nitcorn popcorn popcorn github->popcorn logger logger github->logger ...json ... ...json->json ...popcorn::pop_handlers ... ...popcorn::pop_handlers->popcorn::pop_handlers ...popcorn::pop_validation ... ...popcorn::pop_validation->popcorn::pop_validation ...base64 ... ...base64->base64 ...curl ... ...curl->curl ...nitcorn ... ...nitcorn->nitcorn ...popcorn ... ...popcorn->popcorn ...logger ... ...logger->logger a_star-m a_star-m a_star-m->popcorn::pop_auth

Ancestors

module abstract_collection

core :: abstract_collection

Abstract collection classes and services.
module abstract_text

core :: abstract_text

Abstract class for manipulation of sequences of characters
module api

github :: api

Nit object oriented interface to Github api.
module array

core :: array

This module introduces the standard array structure.
module base64

base64 :: base64

Offers the base 64 encoding and decoding algorithms
module bitset

core :: bitset

Services to handle BitSet
module bytes

core :: bytes

Services for byte streams and arrays
module cache

github :: cache

Enable caching on Github API accesses.
module caching

serialization :: caching

Services for caching serialization engines
module circular_array

core :: circular_array

Efficient data structure to access both end of the sequence.
module codec_base

core :: codec_base

Base for codecs to use with streams
module codecs

core :: codecs

Group module for all codec-related manipulations
module collection

core :: collection

This module define several collection classes.
module core

core :: core

Standard classes and methods used by default by Nit programs and libraries.
module csv

csv :: csv

CSV document handling.
module curl

curl :: curl

Data transfer powered by the native curl library
module engine_tools

serialization :: engine_tools

Advanced services for serialization engines
module environ

core :: environ

Access to the environment variables of the process
module error

json :: error

Intro JsonParseError which is exposed by all JSON reading APIs
module error

core :: error

Standard error-management infrastructure.
module exec

core :: exec

Invocation and management of operating system sub-processes.
module file

core :: file

File manipulations (create, read, write, etc.)
module file_server

nitcorn :: file_server

Provides the FileServer action, which is a standard and minimal file server
module fixed_ints

core :: fixed_ints

Basic integers of fixed-precision
module fixed_ints_text

core :: fixed_ints_text

Text services to complement fixed_ints
module flat

core :: flat

All the array-based text representations
module gc

core :: gc

Access to the Nit internal garbage collection mechanism
module hash_collection

core :: hash_collection

Introduce HashMap and HashSet.
module http_errors

nitcorn :: http_errors

Offers ErrorTemplate to display error pages
module http_request

nitcorn :: http_request

Provides the HttpRequest class and services to create it
module http_request_buffer

nitcorn :: http_request_buffer

Http request parsing for buffered inputs.
module http_response

nitcorn :: http_response

Provides the HttpResponse class and http_status_codes
module inspect

serialization :: inspect

Refine Serializable::inspect to show more useful information
module iso8859_1

core :: iso8859_1

Codec for ISO8859-1 I/O
module json

json :: json

Read and write JSON formatted text using the standard serialization services
module kernel

core :: kernel

Most basic classes and methods.
module libevent

libevent :: libevent

Low-level wrapper around the libevent library to manage events on file descriptors
module list

core :: list

This module handle double linked lists
module math

core :: math

Mathematical operations
module md5

md5 :: md5

Native MD5 digest implementation as Text::md5
module media_types

nitcorn :: media_types

Services to identify Internet media types (or MIME types, Content-types)
module meta

meta :: meta

Simple user-defined meta-level to manipulate types of instances as object.
module more_collections

more_collections :: more_collections

Highly specific, but useful, collections-related classes.
module native

core :: native

Native structures for text and bytes
module native_curl

curl :: native_curl

Binding of C libCurl which allow us to interact with network.
module nitcorn

nitcorn :: nitcorn

The nitcorn Web server framework creates server-side Web apps in Nit
module numeric

core :: numeric

Advanced services for Numeric types
module parser_base

parser_base :: parser_base

Simple base for hand-made parsers of all kinds
module pop_handlers

popcorn :: pop_handlers

Route handlers.
module pop_routes

popcorn :: pop_routes

Internal routes representation.
module pop_validation

popcorn :: pop_validation

Quick and easy validation framework for Json inputs
module poset

poset :: poset

Pre order sets and partial order set (ie hierarchies)
module protocol

core :: protocol

module queue

core :: queue

Queuing data structures and wrappers
module range

core :: range

Module for range of discrete objects.
module re

core :: re

Regular expression support for all services based on Pattern
module reactor

nitcorn :: reactor

Core of the nitcorn project, provides HttpFactory and Action
module ropes

core :: ropes

Tree-based representation of a String.
module safe

serialization :: safe

Services for safer deserialization engines
module serialization

serialization :: serialization

General serialization services
module serialization_core

serialization :: serialization_core

Abstract services to serialize Nit objects to different formats
module serialization_read

json :: serialization_read

Services to read JSON: deserialize_json and JsonDeserializer
module serialization_write

json :: serialization_write

Services to write Nit objects to JSON strings: serialize_to_json and JsonSerializer
module server_config

nitcorn :: server_config

Classes and services to configure the server
module sessions

nitcorn :: sessions

Automated session management
module signal_handler

nitcorn :: signal_handler

Handle SIGINT and SIGTERM to close the server after all active events
module sorter

core :: sorter

This module contains classes used to compare things and sorts arrays.
module static

json :: static

Static interface to read Nit objects from JSON strings
module store

json :: store

Store and load json data.
module stream

core :: stream

Input and output streams of characters
module template

template :: template

Basic template system
module text

core :: text

All the classes and methods related to the manipulation of text entities
module time

core :: time

Management of time and dates
module token

nitcorn :: token

Simple generate_token service, independent of the rest of the nitcorn framework
module union_find

core :: union_find

union–find algorithm using an efficient disjoint-set data structure
module utf8

core :: utf8

Codec for UTF-8 I/O
module vararg_routes

nitcorn :: vararg_routes

Routes with parameters.

Parents

module github

github :: github

Nit wrapper for Github API
module pop_json

popcorn :: pop_json

Introduce useful services for JSON REST API handlers.
module pop_sessions

popcorn :: pop_sessions

Session handlers

Children

module a_star-m

a_star-m

# Authentification handlers.
#
# For now, only Github OAuth is provided.
#
# See https://developer.github.com/v3/oauth/.
#
# This module provide 4 base classes that can be used together to implement a
# Github OAuth handshake.
#
# Here an example of application using the Github Auth as login mechanism.
#
# There is 4 available routes:
# * `/login`: redirects the user to the Github OAuth login page (see `GithubLogin`)
# * `/profile`: shows the currently logged in user (see `Profile Handler`)
# * `/logout`: logs out the user by destroying the entry from the session (see `GithubLogout`)
# * `/oauth`: callback url for Github service after player login (see `GithubOAuthCallBack`)
#
# Routes redirection are handled at the OAuth service registration. Please see
# https://developer.github.com/v3/oauth/#redirect-urls for more niformation on how
# to configure your service to provide smouth redirections beween your routes.
#
# ~~~
# import popcorn
# import popcorn::pop_auth
#
# class ProfileHandler
#	super Handler
#
#	redef fun get(req, res) do
#		var session = req.session
#		if session == null then
#			res.send "No session :("
#			return
#		end
#		var user = session.user
#		if user == null then
#			res.send "Not logged in"
#			return
#		end
#		res.send "<h1>Hello {user.login}</h1>"
#	end
# end
#
# var client_id = "github client id"
# var client_secret = "github client secret"
#
# var app = new App
# app.use("/*", new SessionInit)
# app.use("/login", new GithubLogin(client_id))
# app.use("/oauth", new GithubOAuthCallBack(client_id, client_secret))
# app.use("/logout", new GithubLogout)
# app.use("/profile", new ProfileHandler)
# app.listen("localhost", 3000)
# ~~~
#
# Optionaly, you can use the `GithubUser` handler to provide access to the
# Github user stored in session:
#
# ~~~
# app.use("/api/user", new GithubUser)
# ~~~
module pop_auth

import pop_json
import pop_sessions
import github

# Github OAuth login handler.
#
# See https://developer.github.com/v3/oauth/.
class GithubLogin
	super Handler

	# Client ID delivered by GitHub for your application.
	#
	# See https://github.com/settings/applications/new.
	var client_id: String is writable

	# The URL in your application where users will be sent after authorization.
	#
	# If `null`, the URL used in application registration will be used.
	#
	# See https://developer.github.com/v3/oauth/#redirect-urls.
	var redirect_uri: nullable String = null is writable

	# A space delimited list of scopes.
	#
	# See https://developer.github.com/v3/oauth/#scopes.
	var scope: nullable String = null is writable

	# An optional and unguessable random string.
	#
	# It is used to protect against cross-site request forgery attacks.
	var state: nullable String = null is writable

	# Allow signup at login.
	#
	# Whether or not unauthenticated users will be offered an option to sign up
	# for GitHub during the OAuth flow. The default is true.
	#
	# Use false in the case that a policy prohibits signups.
	var allow_signup = true is writable

	# Github OAuth login URL.
	var auth_url = "https://github.com/login/oauth/authorize" is writable

	# Build Github URL to OAuth service.
	fun build_auth_redirect: String do
		var url = "{auth_url}?client_id={client_id}&allow_signup={allow_signup}"
		var redirect_uri = self.redirect_uri
		if redirect_uri != null then url = "{url}&redirect_uri={redirect_uri}"
		var scope = self.scope
		if scope != null then url = "{url}&scope={scope}"
		var state = self.state
		if state != null then url = "{url}&state={state}"
		return url
	end

	redef fun get(req, res) do res.redirect build_auth_redirect
end

# Get the authentification code and translate it to an access token.
class GithubOAuthCallBack
	super Handler

	# The client ID delivered by GitHub for your application.
	#
	# See https://github.com/settings/applications/new.
	var client_id: String is writable

	# The client secret you received from Github when your registered your application.
	var client_secret: String is writable

	# The URL in your application where users will be sent after authorization.
	#
	# If `null`, the URL used in application registration will be used.
	#
	# See https://developer.github.com/v3/oauth/#redirect-urls.
	var redirect_uri: nullable String is writable

	# An optional and unguessable random string.
	#
	# It is used to protect against cross-site request forgery attacks.
	var state: nullable String is writable

	# Github OAuth token URL.
	var token_url = "https://github.com/login/oauth/access_token" is writable

	# Header map sent with the OAuth token request.
	var headers: HeaderMap do
		var map = new HeaderMap
		map["Accept"] = "application/json"
		return map
	end

	# Build the OAuth post data.
	fun build_auth_body(code: String): HeaderMap do
		var map = new HeaderMap
		map["client_id"] = client_id
		map["client_secret"] = client_secret
		map["code"] = code
		var redirect_uri = self.redirect_uri
		if redirect_uri != null then map["redirect_uri"] = redirect_uri
		var state = self.state
		if state != null then map["state"] = state
		return map
	end

	redef fun get(req, res) do
		# Get OAuth code
		var code = req.string_arg("code")
		if code == null then
			res.error 401
			return
		end

		# Exchange it for an access token
		var access_token = request_access_token(code)
		if access_token == null then
			res.error 401
			return
		end

		# Load github user
		var gh_api = new GithubAPI(access_token)
		var user = gh_api.get_auth_user
		if user == null then
			res.error 401
			return
		end
		# Set session and redirect to user page
		var session = req.session
		if session == null then
			res.error 500
			return
		end
		session.user = user
		res.redirect redirect_uri or else "/"
	end

	# Request an access token from an access `code`.
	private fun request_access_token(code: String): nullable String do
		var request = new CurlHTTPRequest(token_url)
		request.headers = headers
		request.data = build_auth_body(code)
		var response = request.execute
		return parse_token_response(response)
	end

	# Parse the Github access_token response and extract the access_token.
	private fun parse_token_response(response: CurlResponse): nullable String do
		if response isa CurlResponseFailed then
			print "Request to Github OAuth failed"
			print "Requested URI: {token_url}"
			print "Error code: {response.error_code}"
			print "Error msg: {response.error_msg}"
			return null
		else if response isa CurlResponseSuccess then
			var obj = response.body_str.parse_json
			if not obj isa JsonObject then
				print "Error: Cannot parse json response"
				print response.body_str
				return null
			end
			var access_token = obj.get_or_null("access_token")
			if not access_token isa String then
				print "Error: No `access_token` key in response"
				print obj.to_json
				return null
			end
			return access_token
		end
		return null
	end
end

# Destroy user session and redirect to homepage.
class GithubLogout
	super Handler

	# The URL in your application where users will be sent after logout.
	#
	# If `null`, the root uri `/` will be used.
	var redirect_uri: nullable String is writable

	redef fun get(req, res) do
		var session = req.session
		if session != null then
			session.user = null
		end
		res.redirect redirect_uri or else "/"
	end
end

# AuthHandler allows access to session user
#
# Inherit this handler to access to session user from your custom handler.
#
# For example, you need a profile handler that checks if the user is logged
# before returning it in json format.
# ~~~
# import popcorn::pop_auth
#
# class ProfileHandler
#	super AuthHandler
#
#	redef fun get(req, res) do
#		var user = check_session_user(req, res)
#		if user == null then return
#		res.json user
#	end
# end
# ~~~
#
# By using `check_session_user`, we delegate to the `AuthHandler` the responsability
# to set the HTTP 403 error.
# We then check is the user is not null before pursuing.
abstract class AuthHandler
	super Handler

	# Returns `user` from `req.session` or null if no user is authenticated.
	fun session_user(req: HttpRequest): nullable User do
		var session = req.session
		if session == null then return null
		var user = session.user
		return user
	end

	# Check the session for user and return it.
	#
	# If no `user` can be found in session, set res as a HTTP 403 error and return `null`.
	fun check_session_user(req: HttpRequest, res: HttpResponse): nullable User do
		var user = session_user(req)
		if user == null then
			res.error 403
		end
		return user
	end
end

# Get the currently logged in user from session.
class GithubUser
	super AuthHandler

	redef fun get(req, res) do
		var user = check_session_user(req, res)
		if user == null then return
		res.json user
	end
end

redef class Session

	# Github user if logged in.
	var user: nullable User = null is writable
end
lib/popcorn/pop_auth.nit:17,1--332,3