changeset 16:9851bc87b0d7

Beginning of OpenidUser
author Adam Chlipala <adam@chlipala.net>
date Thu, 06 Jan 2011 12:48:13 -0500 (2011-01-06)
parents 35bc4da563dd
children df2eb629f21a
files src/ur/lib.urp src/ur/openidUser.ur src/ur/openidUser.urs
diffstat 3 files changed, 194 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/src/ur/lib.urp	Sun Jan 02 11:22:30 2011 -0500
+++ b/src/ur/lib.urp	Thu Jan 06 12:48:13 2011 -0500
@@ -14,3 +14,4 @@
 $/option
 $/list
 openid
+openidUser
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ur/openidUser.ur	Thu Jan 06 12:48:13 2011 -0500
@@ -0,0 +1,159 @@
+functor Make(M: sig
+                 con cols :: {Type}
+                 constraint [Id] ~ cols
+
+                 val sessionLifetime : int
+                 val afterLogout : url
+                 val secureCookies : bool
+                 val association : Openid.association_mode
+                 val realm : option string
+             end) = struct
+
+    type user = string
+    val show_user = _
+    val inj_user = _
+
+    table user : ([Id = user] ++ M.cols)
+      PRIMARY KEY Id
+
+    table identity : {User : user, Identifier : string}
+      PRIMARY KEY (User, Identifier)
+
+    sequence sessionIds
+
+    table session : {Id : int, Key : int, Identifier : option string, Expires : time}
+      PRIMARY KEY Id
+
+    cookie signingUp : {Session : int, Key : int}
+    cookie login : {User : user, Session : int, Key : int}
+
+    val current =
+        login <- getCookie login;
+        case login of
+            None => return None
+          | Some login =>
+            ident <- oneOrNoRowsE1 (SELECT (session.Identifier)
+                                    FROM session
+                                    WHERE session.Id = {[login.Session]}
+                                      AND session.Key = {[login.Key]});
+            case ident of
+                None => error <xml>Invalid or expired session</xml>
+              | Some None => return None
+              | Some (Some ident) =>
+                valid <- oneRowE1 (SELECT COUNT( * ) > 0
+                                   FROM identity
+                                   WHERE identity.User = {[login.User]}
+                                     AND identity.Identifier = {[ident]});
+                if valid then
+                    return (Some login.User)
+                else
+                    error <xml>Session not authorized to act as user</xml>
+
+    fun main wrap =
+        let
+            fun logout () =
+                clearCookie login;
+                redirect M.afterLogout
+
+            fun opCallback after ses res =
+                case res of
+                    Openid.Canceled => error <xml>You canceled the login process.</xml>
+                  | Openid.Failure s => error <xml>Login failed: {[s]}</xml>
+                  | Openid.AuthenticatedAs ident =>
+                    signup <- getCookie signingUp;
+                    case signup of
+                        Some signup =>
+                        if signup.Session <> ses then
+                            error <xml>Session has changed suspiciously</xml>
+                        else
+                            invalid <- oneRowE1 (SELECT COUNT( * ) = 0
+                                                 FROM session
+                                                 WHERE session.Id = {[signup.Session]}
+                                                   AND session.Key = {[signup.Key]});
+                            if invalid then
+                                error <xml>Invalid or expired session</xml>
+                            else
+                                return <xml>I now believe that you are {[ident]}.</xml>
+                      | None =>
+                        login <- getCookie login;
+                        case login of
+                            None => error <xml>Missing session cookie</xml>
+                          | Some login =>
+                            if login.Session <> ses then
+                                error <xml>Session has changed suspiciously</xml>
+                            else
+                                invalid <- oneRowE1 (SELECT COUNT( * ) = 0
+                                                     FROM session
+                                                     WHERE session.Id = {[login.Session]}
+                                                       AND session.Key = {[login.Key]});
+                                if invalid then
+                                    error <xml>Invalid or expired session</xml>
+                                else
+                                    dml (UPDATE session
+                                         SET Identifier = {[Some ident]}
+                                         WHERE Key = {[login.Key]});
+                                    redirect (bless after)
+
+            fun newSession () =
+                ses <- nextval sessionIds;
+                now <- now;
+                key <- rand;
+                dml (INSERT INTO session (Id, Key, Identifier, Expires)
+                     VALUES ({[ses]}, {[key]}, NULL, {[addSeconds now M.sessionLifetime]}));
+                return {Session = ses, Key = key}
+
+            fun logon r =
+                ident <- oneOrNoRowsE1 (SELECT (identity.Identifier)
+                                        FROM identity
+                                        WHERE identity.User = {[r.User]}
+                                        LIMIT 1);
+                case ident of
+                    None => error <xml>Username not found</xml>
+                  | Some ident =>
+                    ses <- newSession ();
+                    setCookie login {Value = r ++ ses,
+                                     Expires = None,
+                                     Secure = M.secureCookies};
+                    after <- currentUrl;
+                    after <- return (show after);
+                    ses <- return ses.Session;
+                    msg <- Openid.authenticate (opCallback after ses)
+                           {Association = M.association,
+                            Realm = M.realm,
+                            Identifier = ident};
+                    error <xml>Login with your identity provider failed: {[msg]}</xml>
+
+            fun doSignup after r =
+                ses <- newSession ();
+                setCookie signingUp {Value = ses,
+                                     Expires = None,
+                                     Secure = M.secureCookies};
+                ses <- return ses.Session;
+                msg <- Openid.authenticate (opCallback after ses)
+                                           {Association = M.association,
+                                            Realm = M.realm,
+                                            Identifier = r.Identifier};
+                error <xml>Login with your identity provider failed: {[msg]}</xml>
+
+            fun signup () =
+                after <- currentUrl;
+                wrap "Account Signup" <xml>
+                  <form>
+                    OpenID Identifier: <textbox{#Identifier}/><br/>
+                    <submit value="Sign Up" action={doSignup (show after)}/>
+                  </form>
+                </xml>
+        in
+            cur <- current;
+            case cur of
+                Some cur => return <xml>Logged in as {[cur]}. <a link={logout ()}>[Log out]</a></xml>
+              | None => return <xml>
+                <form><textbox{#User}/> <submit value="Log In" action={logon}/></form>
+                <a link={signup ()}>Sign up</a>
+              </xml>
+        end
+
+    task periodic 60 = fn () => dml (DELETE FROM session
+                                     WHERE Expires >= CURRENT_TIMESTAMP)
+
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ur/openidUser.urs	Thu Jan 06 12:48:13 2011 -0500
@@ -0,0 +1,34 @@
+functor Make(M: sig
+                 con cols :: {Type}
+                 constraint [Id] ~ cols
+                 (* Extra columns to add to the user database table *)
+
+                 val sessionLifetime : int
+                 (* Number of seconds a session may live *)
+
+                 val afterLogout : url
+                 (* Where to send the user after he logs out *)
+
+                 val secureCookies : bool
+                 (* Should authentication cookies be restricted to SSL connections? *)
+
+                 val association : Openid.association_mode
+                 (* OpenID cryptography preferences *)
+
+                 val realm : option string
+                 (* See end of [Openid] module's documentation for the meaning of realms *)
+             end) : sig
+
+    type user
+    val show_user : show user
+    val inj_user : sql_injectable_prim user
+
+    table user : ([Id = user] ++ M.cols)
+      PRIMARY KEY Id
+
+    val current : transaction (option user)
+
+    val main : (string -> xbody -> transaction page) -> transaction xbody
+    (* Pass in your generic page template; get out the HTML snippet for user management *)
+
+end