!7 sS passwordSalt() { ret loadTextFileOrCreateWithRandomID(javaxSecretDir("password-salt")); } concept User { S name; SecretValue passwordMD5; S contact; // e.g. mail address bool isMaster; toString { ret or2(name, super.toString()); } } extend AuthedDialogID { new Ref user; // who is logged in } concept UserCreatedObject { new Ref creator; S nameInRepo; // unique by user, for URL bool isPublic = true; } concept UserPost > UserCreatedObject { S type, text; S text() { ret text; } } concept PostReference { new Ref post; new Ref referenced; S as; } !include once #1029928 // new Compact Module include /*c*/module GazelleExamples > DynNewBot2 { void start { botName = heading = "Gazelle Examples"; adminName = heading + " Admin"; set enableUsers; set useWebSockets; set showRegisterLink; unset showTalkToBotLink; passwordSalt(); // make early super.start(); indexConceptFieldCI(User, "name"); } L crudClasses(Req req) { L l = super.crudClasses(req); l.add(UserPost); if (req.masterAuthed) l.add(User); ret l; } S serveRegisterForm(SS params) { S user = trim(params.get("user")); S pw = trim(params.get("pw")); S pw2 = trim(params.get("pw2")); S redirect = params.get("redirect"); redirect = dropParamFromURL(redirect, "logout"); // don't log us out right again if (empty(redirect)) redirect = baseLink + "/"; new LS msgs; if (nempty(user) || nempty(pw)) { lock dbLock(); if (empty(user)) msgs.add("Please enter a user name"); else if (l(user) < 4) msgs.add("Minimum user name length: 4 characters"); else if (l(user) > 30) msgs.add("Maximum user name length: 30 characters"); if (regexpContainsIC("[^a-z0-9\\-\\.]", user)) msgs.add("Bad characters in user name (please use only a-z, 0-9, - and .)"); if (empty(pw)) msgs.add("Please enter a password"); else if (l(pw) < 6) msgs.add("Minimum password length: 6 characters"); if (neq(pw, pw2)) msgs.add("Passwords don't match"); if (hasConceptIC User(name := user)) msgs.add("A user with this name exists, please choose another one"); if (empty(msgs)) { cnew User(name := user, passwordMD5 := SecretValue(hashPW(pw))); ret hrefresh(5.0, redirect) + "User " + user + " created! Redirecting..."; } } ret hhtml(hhead(htitle("Register new user") + hsansserif() + hmobilefix() + hresponstable()) + hbody(hfullcenter( h3_htmlEncode(adminName + " | Register new user") + hpostform( hhidden(+redirect) + tag table( (empty(msgs) ? "" : tr(td() + td() + td(htmlEncode2_nlToBr(lines_rtrim(msgs))))) + tr(td_valignTop("Choose a user name:") + td_valignTop(hinputfield(+user) + "
" + "(Minimum length 4. Characters allowed: a-z, 0-9, - and .)")) + tr(td_valignTop("Choose a password:") + td_valignTop(hpassword(+pw) + "
" + "(Minimum length 6 characters)")) + tr(td_valignTop("Repeat the password please:") + td(hpassword(+pw2))) + tr(td() + td(hsubmit("Register"))), class := "responstable"), action := baseLink + "/register") ))); } bool calcMasterAuthed(Req req) { ret super.calcMasterAuthed(req) || req.auth != null && req.auth.user.has() && req.auth.user->isMaster; } O serveOtherPage(Req req) { S uri = req.uri; new Matches m; if (eq(uri, "/register")) ret serveRegisterForm(req.params); if (eq(uri, "/becomeMaster") && req.auth != null && req.auth.user != null) { S pw = trim(req.params.get("masterPW")); if (eq(pw, realPW())) { cset(req.auth.user, isMaster := true); ret "You are master, " + req.auth.user + "!"; } if (nempty(pw)) ret "Bad master PW"; ret hhtml(hhead(htitle("Become master") + hsansserif() + hmobilefix() + hresponstable()) + hbody(hfullcenter( h3_htmlEncode(adminName + " | Become master") + hpostform( tag table( tr(td_valignTop("You are:") + td_valignTop(htmlEncode2(req.auth.user->name))) + tr(td_valignTop("Enter the master password:") + td_valignTop(hpassword(masterPW := pw))) + tr(td() + td(hsubmit("Become master user"))), class := "responstable"), action := baseLink + "/becomeMaster") ))); } if (swic(uri, "/text/", m)) { long id = parseLong(m.rest()); UserPost post = getConceptOpt UserPost(id); if (post == null) ret serve404("Post with ID " + id + " not found"); ret serveText(post.text()); } ret super.serveOtherPage(req); } // handle user log-in (create/update AuthedDialogID concept) O handleAuth(Req req, S cookie) null { S name = trim(req.params.get('user)), pw = trim(req.params.get('pw)); if (nempty(name) && nempty(pw) && nempty(cookie)) { Domain authDomain; User user = conceptWhereCI User(+name); if (user == null) ret errorMsg("User " + htmlEncode2(name) + " not found"); if (!eq(getVar(user.passwordMD5), hashPW(pw))) ret errorMsg("Bad password, please try again"); // auth cset(uniq AuthedDialogID(+cookie), restrictedToDomain := null, master := user.isMaster, +user); S redirect = req.params.get('redirect); if (nempty(redirect)) ret hrefresh(redirect); } } S hashPW(S pw) { ret md5(pw + passwordSalt()); } MapSO filtersForClass(Class c, Req req) { if (c == UserPost) { User user = req.auth.user!; if (!req.masterAuthed && !user.isMaster) ret litmap(creator := user); } ret super.filtersForClass(c, req); } S loggedInUserDesc(Req req) { ret req.auth == null ? "not logged in" : req.auth.user! == null ? (req.masterAuthed ? "master user" : "weird user") : "user " + req.auth.user->name; } HCRUD_Concepts crudData(Class c) { HCRUD_Concepts cc = super.crudData(c); if (c == UserPost) { cc.addRenderer("text", new HCRUD_Data.TextArea(80, 10)); cc.fieldHelp( type := "Type of post (any format)", text := "Text contents of this post (any format)"); } ret cc; } S authFormHeading() { ret super.authFormHeading() + p(hsnippetimg_scaleToWidth(200, #1101484, 425, 257, title := "Sleepy Gazelle")); } } // end of module