passwd : string, status : status, applied : Init.C.timestamp,
ipaddr : string option,
confirmed : Init.C.timestamp option, decided : Init.C.timestamp option,
- msg : string }
+ msg : string, unix_passwd : string }
val lookupApp : int -> app
val listApps : status -> app list
val approve : int * string -> bool
val add : int -> unit
val abortAdd : int -> unit
-end
\ No newline at end of file
+end
forward : bool, uses : string, other : string,
passwd : string, status : status, applied : C.timestamp, ipaddr : string option,
confirmed : C.timestamp option, decided : C.timestamp option,
- msg : string}
+ msg : string, unix_passwd : string}
fun mkAppRow [id, name, rname, gname, email, forward, uses, other, passwd, status,
- applied, ipaddr, confirmed, decided, msg] =
+ applied, ipaddr, confirmed, decided, msg, unix_passwd] =
{ id = C.intFromSql id, name = C.stringFromSql name, rname = C.stringFromSql rname,
gname = (if C.isNull gname then NONE else SOME (C.stringFromSql gname)),
email = C.stringFromSql email, forward = C.boolFromSql forward,
ipaddr = (if C.isNull ipaddr then NONE else SOME (C.stringFromSql ipaddr)),
confirmed = if C.isNull confirmed then NONE else SOME (C.timestampFromSql confirmed),
decided = if C.isNull decided then NONE else SOME (C.timestampFromSql decided),
- msg = C.stringFromSql msg}
+ msg = C.stringFromSql msg, unix_passwd = C.stringFromSql unix_passwd}
| mkAppRow r = rowError ("app", r)
fun lookupApp id =
- case C.oneOrNoRows (getDb ()) ($`SELECT id, name, rname, gname, email, forward, uses, other, passwd, status, applied, ipaddr, confirmed, decided, msg
+ case C.oneOrNoRows (getDb ()) ($`SELECT id, name, rname, gname, email, forward, uses, other, passwd, status, applied, ipaddr, confirmed, decided,
+ msg, unix_passwd
FROM MemberApp
WHERE id = ^(C.intToSql id)`) of
SOME row => mkAppRow row
| NONE => raise Fail "Membership application not found"
fun listApps status =
- C.map (getDb ()) mkAppRow ($`SELECT id, name, rname, gname, email, forward, uses, other, passwd, status, applied, ipaddr, confirmed, decided, msg
+ C.map (getDb ()) mkAppRow ($`SELECT id, name, rname, gname, email, forward, uses, other, passwd, status, applied, ipaddr, confirmed, decided,
+ msg, unix_passwd
FROM MemberApp
WHERE status = ^(statusToSql status)
AND NOT (status = 2 AND decided < CURRENT_TIMESTAMP - INTERVAL '1 MONTH')
val validUser : string -> bool
val userExists : string -> bool
- val confirm : int * string -> bool
-end
\ No newline at end of file
+ val confirm : int * string -> string option
+end
structure App :> APP =
struct
-val baseUrl = "http://join.hcoop.net/join/"
+val baseUrl = "https://join.hcoop.net/join/"
val portalUrl = "https://members2.hcoop.net/portal/"
open Sql
type application = { name : string, rname : string, gname : string option, email : string,
forward : bool, uses : string, other : string }
+fun randomPassword () =
+ let
+ val proc = Unix.execute ("/usr/bin/apg", ["/usr/bin/apg", "-n", "1", "-m", "10"])
+ in
+ case TextIO.inputLine (Unix.textInstreamOf proc) of
+ NONE => raise Fail "Couldn't execute apg"
+ | SOME line =>
+ case String.tokens Char.isSpace line of
+ [s] => s
+ | _ => raise Fail "Couldn't parse output of apg"
+ end
+
fun apply {name, rname, gname, email, forward, uses, other} =
let
val db = getDb ()
let
val id = C.intFromSql id
val passwd = Int.toString (Int.abs (Random.randInt (!rnd)))
+ val unix_passwd = randomPassword ()
in
- C.dml db ($`INSERT INTO MemberApp (id, name, rname, gname, email, forward, uses, other, passwd, status, applied, msg)
+ C.dml db ($`INSERT INTO MemberApp (id, name, rname, gname, email, forward, uses, other, passwd, status, applied, msg, unix_passwd)
VALUES (^(C.intToSql id), ^(C.stringToSql name), ^(C.stringToSql rname),
^(case gname of NONE => "NULL" | SOME gname => C.stringToSql gname),
^(C.stringToSql email), ^(C.boolToSql forward), ^(C.stringToSql uses),
- ^(C.stringToSql other), ^(C.stringToSql passwd), 0, CURRENT_TIMESTAMP, '')`);
+ ^(C.stringToSql other), ^(C.stringToSql passwd), 0, CURRENT_TIMESTAMP,
+ '', ^(C.stringToSql unix_passwd))`);
sendMail (email, "Confirm membership application",
- "We've received a request to join the Internet Hosting Cooperative (hcoop.net) with this e-mail address.",
+ "We've received a request to join the Internet Hosting Cooperative (hcoop.net) with this e-mail address.",
fn mwrite => (mwrite ("To confirm this application, visit ");
mwrite (baseUrl);
mwrite ("confirm?id=");
mwrite (Int.toString id);
mwrite ("&p=");
- mwrite (passwd);
+ mwrite passwd;
mwrite ("\n")),
- id)
+ id)
end
| _ => raise Fail "Bad next sequence val"
end
let
val db = getDb ()
in
- case C.oneOrNoRows db ($`SELECT id FROM MemberApp WHERE id = ^(C.intToSql id) AND passwd = ^(C.stringToSql passwd) AND status = 0`) of
- SOME _ =>
+ case C.oneOrNoRows db ($`SELECT unix_passwd FROM MemberApp WHERE id = ^(C.intToSql id) AND passwd = ^(C.stringToSql passwd) AND status = 0`) of
+ SOME [unix_passwd] =>
(C.dml db ($`UPDATE MemberApp SET status = 1, confirmed = CURRENT_TIMESTAMP WHERE id = ^(C.intToSql id)`);
- sendMail ("board@hcoop.net",
- "New membership application",
- "We've received a new request to join hcoop.",
+ if sendMail ("board@hcoop.net",
+ "New membership application",
+ "We've received a new request to join hcoop.",
fn mwrite => (mwrite ("Open applications: ");
mwrite (portalUrl);
mwrite ("apps")),
- id))
- | NONE => false
+ id) then
+ SOME (C.stringFromSql unix_passwd)
+ else
+ NONE)
+ | NONE => NONE
end
end
-<% @header [("title", ["Confirm application"])];
-
-val id = Web.stoi ($"id");
-val passwd = $"p";
-
-if App.confirm (id, passwd) then
- %><h3><b>Confirmation successful</b></h3>
- You should hear from us within a few days from now.<%
-else
- %><h3><b>Error confirming</b></h3>
- Did you already follow this confirmation link?<%
-end;
-
-@footer[] %>
\ No newline at end of file
+<% @header [("title", ["Confirm application"])];
+
+val id = Web.stoi ($"id");
+val passwd = $"p";
+
+switch App.confirm (id, passwd) of
+ SOME unix_passwd =>
+ %><h3><b>Confirmation successful</b></h3>
+ You should hear from us within a few days from now. Save this password, to use to access our servers if your application is approved:
+ <blockquote><tt><% Web.html unix_passwd %></tt></blockquote><%
+ | NONE =>
+ %><h3><b>Error confirming</b></h3>
+ Did you already follow this confirmation link?<%
+end;
+
+@footer[] %>
\ No newline at end of file
elseif not (App.validEmail email) then
%><h3>Invalid e-mail address</h3><%
elseif not (App.apply { name = name, rname = rname, email = email,
- gname = (case gname of "" => NONE | _ => SOME gname),
+ gname = (case gname of "" => NONE | _ => SOME gname),
forward = forward, uses = uses, other = other }) then
%><h3>Error sending confirmation e-mail</h3><%
else
- %><h3>Application recorded</h3>
+ %><h3>Application recorded</h3>
Check your e-mail for a message with further instructions.<%
end
else
ipaddr TEXT,
confirmed TIMESTAMP,
decided TIMESTAMP,
- msg TEXT NOT NULL);
+ msg TEXT NOT NULL,
+ unix_passwd TEXT NOT NULL);
CREATE SEQUENCE MemberAppSeq START 1;