furnace.sessions
furnace.referrer
furnace.db
-furnace.auth.providers ;
+furnace.auth.providers
+furnace.auth.login.permits ;
IN: furnace.alloy
: <alloy> ( responder db params -- responder' )
<check-form-submissions>
] call ;
-: state-classes { session flash-scope aside } ; inline
+: state-classes { session flash-scope aside permit } ; inline
: init-furnace-tables ( -- )
state-classes ensure-tables
--- /dev/null
+USING: furnace.auth tools.test ;
+IN: furnace.auth.tests
+
+\ logged-in-username must-infer
+\ <protected> must-infer
+\ new-realm must-infer
: save-user-after ( user -- )\r
<user-saver> &dispose drop ;\r
\r
-: init-user ( realm -- )\r
- logged-in-username [\r
- users get-user\r
- [ logged-in-user set ] [ save-user-after ] bi\r
- ] when* ;\r
+: init-user ( user -- )\r
+ [ [ logged-in-user set ] [ save-user-after ] bi ] when* ;\r
\r
M: realm call-responder* ( path responder -- response )\r
dup realm set\r
- dup init-user\r
+ dup logged-in-username dup [ users get-user ] when init-user\r
call-next-method ;\r
\r
: encode-password ( string salt -- bytes )\r
! Copyright (c) 2007 Chris Double.\r
! See http://factorcode.org/license.txt for BSD license.\r
-USING: accessors kernel splitting base64 namespaces\r
+USING: accessors kernel splitting base64 namespaces strings\r
http http.server.responses furnace.auth ;\r
IN: furnace.auth.basic\r
\r
TUPLE: basic-auth-realm < realm ;\r
\r
-C: <basic-auth-realm> basic-auth-realm\r
+: <basic-auth-realm> ( responder name -- realm )\r
+ basic-auth-realm new-realm ;\r
\r
: parse-basic-auth ( header -- username/f password/f )\r
dup [\r
" " split1 swap "Basic" = [\r
- base64> ":" split1\r
+ base64> >string ":" split1\r
] [ drop f f ] if\r
] [ drop f f ] if ;\r
\r
name>> <401> ;\r
\r
M: basic-auth-realm logged-in-username ( realm -- uid )\r
+ drop\r
request get "authorization" header parse-basic-auth\r
- dup [ over realm get check-login swap and ] [ 2drop f ] if ;\r
+ dup [ over check-login swap and ] [ 2drop f ] if ;\r
: <register-action> ( -- action )
<page-action>
- { realm "register" } >>template
+ { realm "features/registration/register" } >>template
[
{
! Copyright (c) 2008 Slava Pestov\r
! See http://factorcode.org/license.txt for BSD license.\r
-USING: kernel accessors namespaces validators urls\r
-html.forms\r
-http.server.dispatchers\r
+USING: kernel accessors namespaces sequences math.parser\r
+calendar validators urls html.forms\r
+http http.server http.server.dispatchers\r
+furnace\r
furnace.auth\r
furnace.flash\r
furnace.asides\r
furnace.actions\r
furnace.sessions\r
-furnace.utilities ;\r
+furnace.utilities\r
+furnace.auth.login.permits ;\r
IN: furnace.auth.login\r
\r
-TUPLE: login-realm < realm ;\r
+SYMBOL: permit-id\r
+\r
+: permit-id-key ( realm -- string )\r
+ [ >hex 2 CHAR: 0 pad-left ] { } map-as concat\r
+ "__p_" prepend ;\r
+\r
+: client-permit-id ( realm -- id/f )\r
+ permit-id-key client-state dup [ string>number ] when ;\r
+\r
+TUPLE: login-realm < realm timeout domain ;\r
+\r
+M: login-realm call-responder*\r
+ [ name>> client-permit-id permit-id set ]\r
+ [ call-next-method ]\r
+ bi ;\r
\r
M: login-realm logged-in-username\r
- drop session get uid>> ;\r
+ drop permit-id get dup [ get-permit-uid ] when ;\r
+\r
+M: login-realm modify-form ( responder -- )\r
+ drop permit-id get realm get name>> permit-id-key hidden-form-field ;\r
+\r
+: <permit-cookie> ( -- cookie )\r
+ permit-id get realm get name>> permit-id-key <cookie>\r
+ "$login-realm" resolve-base-path >>path\r
+ realm get timeout>> from-now >>expires\r
+ realm get domain>> >>domain ;\r
\r
-: set-uid ( username -- )\r
- session get [ (>>uid) ] [ (session-changed) ] bi ;\r
+: put-permit-cookie ( response -- response' )\r
+ <permit-cookie> put-cookie ;\r
\r
: successful-login ( user -- response )\r
- username>> set-uid URL" $realm" end-aside ;\r
+ [ username>> make-permit permit-id set ] [ init-user ] bi\r
+ URL" $realm" end-aside\r
+ put-permit-cookie ;\r
\r
-: logout ( -- ) f set-uid URL" $realm" end-aside ;\r
+: logout ( -- )\r
+ permit-id get [ delete-permit ] when*\r
+ URL" $realm" end-aside ;\r
\r
SYMBOL: description\r
SYMBOL: capabilities\r
\r
: <logout-action> ( -- action )\r
<action>\r
- [ logout ] >>submit ;\r
+ [ logout ] >>submit\r
+ <protected>\r
+ "logout" >>description ;\r
\r
M: login-realm login-required*\r
drop\r
: <login-realm> ( responder name -- auth )\r
login-realm new-realm\r
<login-action> <auth-boilerplate> "login" add-responder\r
- <logout-action> "logout" add-responder ;\r
+ <logout-action> "logout" add-responder\r
+ 20 minutes >>timeout ;\r
--- /dev/null
+USING: accessors namespaces combinators.lib kernel
+db.tuples db.types
+furnace.auth furnace.sessions furnace.cache ;
+IN: furnace.auth.login.permits
+
+TUPLE: permit < server-state session uid ;
+
+permit "PERMITS" {
+ { "session" "SESSION" BIG-INTEGER +not-null+ }
+ { "uid" "UID" { VARCHAR 255 } +not-null+ }
+} define-persistent
+
+: touch-permit ( permit -- )
+ realm get touch-state ;
+
+: get-permit-uid ( id -- uid )
+ permit get-state {
+ [ ]
+ [ session>> session get id>> = ]
+ [ [ touch-permit ] [ uid>> ] bi ]
+ } 1&& ;
+
+: make-permit ( uid -- id )
+ permit new
+ swap >>uid
+ session get id>> >>session
+ [ touch-permit ] [ insert-tuple ] [ id>> ] tri ;
+
+: delete-permit ( id -- )
+ permit new-server-state delete-tuples ;
IN: furnace.auth.providers.assoc.tests\r
-USING: furnace.actions furnace.auth.providers \r
+USING: furnace.actions furnace.auth furnace.auth.providers \r
furnace.auth.providers.assoc furnace.auth.login\r
tools.test namespaces accessors kernel ;\r
\r
-<action> <login>\r
+<action> "Test" <login-realm>\r
<users-in-memory> >>users\r
-login set\r
+realm set\r
\r
[ t ] [\r
"slava" <user>\r
request get url>>
[ [ protocol>> ] [ host>> ] [ port>> ] tri 3array ] bi@ = ;
+: cookie-client-state ( key request -- value/f )
+ swap get-cookie dup [ value>> ] when ;
+
+: post-client-state ( key request -- value/f )
+ request-params at ;
+
+: client-state ( key -- value/f )
+ request get dup method>> {
+ { "GET" [ cookie-client-state ] }
+ { "HEAD" [ cookie-client-state ] }
+ { "POST" [ post-client-state ] }
+ } case ;
+
SYMBOL: exit-continuation
: exit-with ( value -- )
: session-id-key "__s" ;
-: cookie-session-id ( request -- id/f )
- session-id-key get-cookie
- dup [ value>> string>number ] when ;
-
-: post-session-id ( request -- id/f )
- session-id-key swap request-params at string>number ;
-
-: request-session-id ( -- id/f )
- request get dup method>> {
- { "GET" [ cookie-session-id ] }
- { "HEAD" [ cookie-session-id ] }
- { "POST" [ post-session-id ] }
- } case ;
-
: verify-session ( session -- session )
sessions get verify?>> [
dup [
] when ;
: request-session ( -- session/f )
- request-session-id get-session verify-session ;
+ session-id-key
+ client-state dup [ string>number ] when
+ get-session verify-session ;
-: <session-cookie> ( id -- cookie )
- session-id-key <cookie>
+: <session-cookie> ( -- cookie )
+ session get id>> session-id-key <cookie>
"$sessions" resolve-base-path >>path
sessions get timeout>> from-now >>expires
sessions get domain>> >>domain ;
: put-session-cookie ( response -- response' )
- session get id>> number>string <session-cookie> put-cookie ;
+ <session-cookie> put-cookie ;
M: sessions modify-form ( responder -- )
drop session get id>> session-id-key hidden-form-field ;
drop
] { } make ;
+: check-cookie-string ( string -- string' )
+ dup "=;'\"" intersect empty?
+ [ "Bad cookie name or value" throw ] unless ;
+
: (unparse-cookie) ( key value -- )
{
{ f [ drop ] }
- { t [ , ] }
+ { t [ check-cookie-string , ] }
[
{
{ [ dup timestamp? ] [ timestamp>cookie-string ] }
{ [ dup duration? ] [ dt>seconds number>string ] }
+ { [ dup real? ] [ number>string ] }
[ ]
} cond
- "=" swap 3append ,
+ check-cookie-string "=" swap check-cookie-string 3append ,
]
} case ;
: unparse-cookie ( cookie -- strings )
[
- dup name>> >lower over value>> (unparse-cookie)
+ dup name>> check-cookie-string >lower
+ over value>> (unparse-cookie)
"path" over path>> (unparse-cookie)
"domain" over domain>> (unparse-cookie)
"expires" over expires>> (unparse-cookie)
"index.html" append-path dup exists? [ drop f ] unless ;\r
\r
: serve-directory ( filename -- response )\r
- request get path>> "/" tail? [\r
+ request get url>> path>> "/" tail? [\r
dup\r
find-index [ serve-file ] [ list-directory ] ?if\r
] [\r
<boilerplate>
{ wiki "page-common" } >>template ;
+: init-sidebar ( -- )
+ "Sidebar" latest-revision [
+ "sidebar" [ from-object ] nest-form
+ ] when* ;
+
: <wiki> ( -- dispatcher )
wiki new-dispatcher
<main-article-action> <article-boilerplate> "" add-responder
<list-changes-feed-action> "changes.atom" add-responder
<delete-action> "delete" add-responder
<boilerplate>
- [ "sidebar" [ "Sidebar" latest-revision from-object ] nest-form ] >>init
+ [ init-sidebar ] >>init
{ wiki "wiki-common" } >>template ;