From 4c4200ab6889db8638c0a95a89c2cce0019d1fe9 Mon Sep 17 00:00:00 2001 From: Massimo Melina Date: Tue, 12 May 2020 12:14:41 +0200 Subject: [PATCH] new login system --- default.tpl | 238 ++++++++++++++++++++++++++++++++----------------- main.pas | 141 ++++++++++++++--------------- optionsDlg.dfm | 4 + scriptLib.pas | 4 +- utillib.pas | 2 + 5 files changed, 230 insertions(+), 159 deletions(-) diff --git a/default.tpl b/default.tpl index f4659a5..d6faae9 100644 --- a/default.tpl +++ b/default.tpl @@ -25,19 +25,16 @@ COMMENT with these you can disable some features of the template. Please note th + + + [] {.$common-head.} {.!HFS.} %folder% - - -
@@ -73,10 +70,10 @@ COMMENT with these you can disable some features of the template. Please note th function changePwd() { {.if|{.can change pwd.} | ask(this.innerHTML, 'password', function(s){ - s && ajax('changepwd', {'new':s}, getStdAjaxCB([ - "!{.!Password changed, you'll have to login again..}", - '>~login' - ])) + s && ajax('changepwd', {'new':s}, getStdAjaxCB(function(){ + showMsg("{.!Password changed, you'll have to login again..}") + location = '~login' + })) }) | showError("{.!Sorry, you lack permissions for this action.}") .} @@ -86,7 +83,12 @@ COMMENT with these you can disable some features of the template. Please note th if (!data) data = {}; data.token = "{.cookie|HFS_SID_.}"; - return $.post("?mode=section&id=ajax."+method, data, cb||getStdAjaxCB()); + showLoading() + return $.post("?mode=section&id=ajax."+method, data, function(){ + if (cb) + showLoading(false) + ;(cb||getStdAjaxCB()).apply(this,arguments) + }); }//ajax @@ -97,8 +99,8 @@ COMMENT with these you can disable some features of the template. Please note th
- {.$search panel.} {.$upload panel.}
@@ -434,6 +436,14 @@ z-index:1; /* without this .item-menu will be over*/ } {.123 if 2|
|{.commentNL|%item-comment%.}|
.}
+[login] +{.$common-head.} + {.!HFS.} %folder% + + + + + [error-page] @@ -674,30 +684,47 @@ RegExp.escape = function(text) { return text.replace(arguments.callee.sRE, '\\$1'); }//escape -function dialog(content, cb) { +// options: cb(function), closable(false) +function dialog(content, options) { + options = options||{} + var cb = typeof options==='function' ? options : options.cb + var active = document.activeElement var ret = $('
').html(content).keydown(function(ev) { if (ev.keyCode===27) - ret.close() + close2() }) ret.close = function() { ret.closest('.dialog-overlay').remove() - cb && cb() + $(active).focus() + cb && cb() } + function close2(){ + if (options.closable !== false) + ret.close() + } ret.appendTo( $('
').appendTo('body') - .click(ret.close) + .click(close2) ).click(function(ev){ ev.stopImmediatePropagation() }) + setTimeout(function(){ + ret.find(':input:not(:disabled):first').focus() + }) return ret } // dialog -function showMsg(content, cb) { +// options: cb(function), buttons(jq|false) +function showMsg(content, options) { + options = options||{} + var cb = typeof options==='function' ? options : options.cb + var bs = options.buttons if (~content.indexOf('<')) content = content.replace(/\n/g, '
') var ret = dialog($('
').css({ display:'inline-block', textAlign:'left' }).html(content), cb).css('text-align', 'center') .append( - $('
').html( + bs===false ? null + : $('
').html(bs || $('\ +
\ +
') +} // showAccount + +function logout(){ + $.post('?mode=logout', function(){ + location.reload() + }); +} + +function digest(data, method='SHA-256') { + return crypto.subtle.digest(method, new TextEncoder().encode(data)).then(x=> + Array.from(new Uint8Array(x)).map(b => b.toString(16).padStart(2, '0')).join('').toUpperCase() ) +} // digest + function setCookie(name,value,days) { if (days) { var date = new Date(); @@ -961,7 +1035,7 @@ function sendFiles(files, done) { return Number(s) + 1 }) $(r.err ? ' '+ r.name+'' - : ' '+r.name+'') + : ' '+r.name+'') .appendTo('#upload-results'); }) } diff --git a/main.pas b/main.pas index 6ef3dcf..6216c93 100644 --- a/main.pas +++ b/main.pas @@ -295,7 +295,7 @@ type vars: THashedStringList; created, ttl, expires: Tdatetime; public - id: string; + id, user: string; constructor create(const sid:string=''); destructor Destroy; override; procedure setVar(const k,v:string); @@ -325,7 +325,7 @@ type agent: string; conn: ThttpConn; account: Paccount; - usr, pwd: string; + user, pwd: string; acceptedCredentials: boolean; limiter: TspeedLimiter; tpl: Ttpl; @@ -1326,7 +1326,7 @@ if (action = FA_UPLOAD) and not f.isRealFolder() then exit; result:=TRUE; if stringExists(USER_ANYONE, a, TRUE) then exit; -result:=(cd.usr = '') and stringExists(USER_ANONYMOUS, a, TRUE) +result:=(cd.user = '') and stringExists(USER_ANONYMOUS, a, TRUE) or assigned(cd.account) and stringExists(USER_ANY_ACCOUNT, a, TRUE) or (NIL <> findEnabledLinkedAccount(cd.account, a, TRUE)); end; // accountAllowed @@ -1562,7 +1562,7 @@ var function allowedTo(f:Tfile):boolean; begin if cd = NIL then result:=FALSE - else result:=(not (FA_VIS_ONLY_ANON in f.flags) or (cd.usr = '')) + else result:=(not (FA_VIS_ONLY_ANON in f.flags) or (cd.user = '')) and (seeProtected or f.accessFor(cd)) and not (forArchive and f.isDLforbidden()) end; // allowedTo @@ -1912,7 +1912,7 @@ while i < srv.conns.count do if isDownloading(d) and ((f = NIL) or (assigned(d.lastFile) and d.lastFile.same(f))) and ((ip = '') or addressMatch(ip, d.address)) - and ((user = '') or sameText(user, d.usr)) + and ((user = '') or sameText(user, d.user)) then inc(result); inc(i); @@ -1931,7 +1931,7 @@ while i < srv.conns.count do begin d:=conn2data(i); if not onlyDownloading or isDownloading(d) then - addUniqueString(if_(usersInsteadOfIps, d.usr, d.address), ips); + addUniqueString(if_(usersInsteadOfIps, d.user, d.address), ips); inc(i); end; result:=length(ips); @@ -2362,7 +2362,8 @@ end; // disconnect procedure TconnData.logout(); begin freeAndNIL(session); -usr:=''; +account:=NIL; +user:=''; pwd:=''; conn.delCookie(SESSION_COOKIE); end; // logout @@ -3071,7 +3072,7 @@ begin result:=hasRecursive([attribute], FALSE, outInherited) end; function Tfile.accessFor(cd:TconnData):boolean; begin if cd = NIL then result:=accessFor('', '') -else result:=accessFor(cd.usr, cd.pwd) +else result:=accessFor(cd.user, cd.pwd) end; // accessFor function Tfile.accessFor(username, password:string):boolean; @@ -3539,11 +3540,11 @@ var s:=url; end else - if pwdInPagesChk.Checked and (cd.usr > '') then + if pwdInPagesChk.Checked and (cd.user > '') then begin if encodePwdUrlChk.checked then s:=totallyEncoded(cd.pwd) else s:=encodeURL(cd.pwd); - s:=f.fullURL( encodeURL(cd.usr)+':'+s, getSafeHost(cd) )+fingerprint; + s:=f.fullURL( encodeURL(cd.user)+':'+s, getSafeHost(cd) )+fingerprint; url:=s end else @@ -3823,7 +3824,7 @@ var fn:=macroQuote(fn); // apply fields files:=files+xtpl(t, [ - '%item-user%', macroQuote(d.usr), + '%item-user%', macroQuote(d.user), '%perc%',intToStr( trunc(perc*100) ), '%filename%', fn, '%filename-js%', jsEncode(fn, '''"'), @@ -3908,6 +3909,7 @@ try addUploadSymbols(); addProgressSymbols(); addUploadResultsSymbols(); + //addArray(md.table, ['%folder%', data.f.]); if data = NIL then s:='' else s:=first(data.banReason, data.disconnectReason); addArray(md.table, ['%reason%', s]); @@ -4122,7 +4124,7 @@ var begin decodeDateFully(now(), y,m,d,w); if cd = NIL then u:='' -else u:=nonEmptyConcat('(', cd.usr, ')'); +else u:=nonEmptyConcat('(', cd.user, ')'); result:=xtpl(logFile.filename, [ '%d%', int0(d,2), '%m%', int0(m,2), @@ -4168,18 +4170,15 @@ else addr:=''; if assigned(cd) and assigned(cd.conn) then - begin - addr:=cd.address+':'+cd.conn.port + addr:=nonEmptyConcat('', cd.user, '@') + +cd.address+':'+cd.conn.port +nonEmptyConcat(' {', localDNSget(cd.address), '}'); - if freeLoginChk.checked or cd.acceptedCredentials then - addr:=nonEmptyConcat('', cd.usr, '@')+addr; - end; if (logFile.filename > '') and (logFile.apacheFormat = '') then begin s:=ts; if (cd = NIL) or (cd.conn = nil) then s:=s+TAB+''+TAB+''+TAB+''+TAB+'' - else s:=s+TAB+cd.usr+TAB+cd.address+TAB+cd.conn.port+TAB+localDNSget(cd.address); + else s:=s+TAB+cd.user+TAB+cd.address+TAB+cd.conn.port+TAB+localDNSget(cd.address); s:=s+TAB+first; if tabOnLogFileChk.checked then s:=s+stripChars(reReplace(lines, '^', TAB),[#13,#10]) @@ -4365,7 +4364,7 @@ try case cmd of 'a', 'h': res:=cd.address; 'l': res:='-'; - 'u': res:=first(cd.usr, '-'); + 'u': res:=first(cd.user, '-'); 't': res:='[' +replaceStr(formatDatetime(APACHE_TIMESTAMP_FORMAT, now()), '!!!',MONTH2STR[monthOf(now())]) @@ -4887,6 +4886,8 @@ var begin if data = NIL then exit; + data.user:=''; + data.pwd:=''; if data.session = NIL then begin sid:=conn.getCookie(SESSION_COOKIE); @@ -4901,18 +4902,18 @@ var end; end; data.session.keepAlive(); - if data.usr = '' then + if conn.request.user > '' then // priority begin - data.usr:=data.session.getVar('user'); - data.pwd:=data.session.getVar('password'); - end; - if (data.usr = '') and (conn.request.user > '') then - begin - data.usr:=conn.request.user; + data.user:=conn.request.user; data.pwd:=conn.request.pwd; + data.account:=NIL; + exit; end; - if (data.usr = '') <> (data.account = NIL) then - data.account:=getAccount(data.usr); + data.account:=getAccount(data.session.user); + if data.account = NIL then + exit; + data.user:=data.account.user; + data.pwd:=data.account.pwd; end; // sessionSetup procedure serveTar(); @@ -5059,6 +5060,7 @@ var var dlForbiddenForWholeFolder, specialGrant: boolean; urlCmd: string; + acc: Paccount; function accessGranted(forceFile:Tfile=NIL):boolean; resourcestring @@ -5087,11 +5089,17 @@ var freeIfTemp(Ftemp); end; if result then exit; + if f.isFolder() then + begin + conn.reply.mode:=HRM_REPLY; + getPage('login', data, f); + exit; + end; conn.reply.realm:=f.getShownRealm(); runEventScript('unauthorized'); getPage('unauthorized', data); // log anyone trying to guess the password - if (forceFile = NIL) and stringExists(data.usr, getAccountList(TRUE, FALSE)) + if (forceFile = NIL) and stringExists(data.user, getAccountList(TRUE, FALSE)) and logOtherEventsChk.checked then add2log(FAILED, data); end; // accessGranted @@ -5190,13 +5198,14 @@ var function goodPassword(s:string; func:ThashFunc):boolean; begin - s:=data.postVars.values[s]; - result:=(s > '') and (s = func(func(data.account.pwd)+data.session.id)) + s:=data.postVars.values['password'+s]; + // Instead of hash(pwd+session) I replaced pwd with hash(pwd) so that in the future this may work even if we stored hashed password on the server + result:=(s > '') and (s = func(func(acc.pwd)+data.session.id)) end; var b: boolean; - s: string; + s, mode: string; i: integer; section: PtplSection; begin @@ -5270,6 +5279,7 @@ var url:=conn.request.url; extractParams(); url:=decodeURL(ansistring(url)); + mode:= data.urlvars.values['mode']; data.lastFN:=extractFileName( replaceStr(url,'/','\') ); data.agent:=getAgentID(conn); @@ -5281,50 +5291,31 @@ var end; sessionSetup(); - if data.postVars.indexOfName('__USER') >= 0 then + if mode = 'logout' then begin - s:=data.postVars.values['__USER']; - data.account:=getAccount(s); - if data.account = NIL then - if s = '' then // logout - begin - s:='ok'; - data.logout(); - end - else - s:='username not found' + data.logout(); + replyWithString('ok'); + exit; + end; + if mode = 'login' then + begin + acc:=getAccount(data.postVars.values['user']); + if acc = NIL then + s:='username not found' else - begin - data.usr:=s; - { I opted to use double hashing for this authentication method so that in the - future this may work even if we stored hashed password on the server, - thus being unable to calculate hash(pwd+sessionID). - By relying on hash(pwd) instead of pwd we avoid such problem. } - if goodPassword('__PASSWORD_SHA256', strSHA256) - or goodPassword('__PASSWORD_MD5', strMD5) - or (data.postVars.values['__PASSWORD'] = data.account.pwd) then + if goodPassword('SHA256', strSHA256) + or goodPassword('MD5', strMD5) + or (data.postVars.values['password'] = acc.pwd) then begin + data.session.user:=acc.user; s:='ok'; - data.pwd:=data.account.pwd; - data.session.setVar('user', data.usr); - data.session.setVar('password', data.pwd); end else - begin s:='bad password'; //TODO shouldn't this change http code? - data.account:=NIL; - data.usr:=''; - end; - end; - if data.postVars.values['__AJAX'] = '1' then - begin - replyWithString(s); - exit; - end; + replyWithString(s); + exit; end; - // this is better to be refresh, because a user may be deleted meantime - data.account:=getAccount(data.usr); conn.ignoreSpeedLimit:=noLimitsFor(data.account); // all URIs must begin with / @@ -5349,7 +5340,7 @@ var getPage('not found', data); exit; end; - if data.urlvars.values['mode'] = 'jquery' then + if mode = 'jquery' then begin if notModified(conn,'jquery'+FloatToStr(uptime), '') then exit; @@ -5360,9 +5351,9 @@ var // forbid using invalid credentials if not freeLoginChk.checked and not specialGrant then - if (data.usr>'') + if (data.user>'') and ((data.account=NIL) or (data.account.pwd <> data.pwd)) - and not usersInVFS.match(data.usr, data.pwd) then + and not usersInVFS.match(data.user, data.pwd) then begin data.acceptedCredentials:=FALSE; runEventScript('unauthorized'); @@ -5401,7 +5392,7 @@ var conn.reply.url:=f.url(); // we use f.url() instead of just appending a "/" to url because of problems with non-ansi chars http://www.rejetto.com/forum/?topic=7837 exit; end; - if f.isFolder() and (urlCmd = '') and (data.urlvars.indexOfName('mode')<0) then + if f.isFolder() and (urlCmd = '') and (mode='') then switchToDefaultFile(); if enableNoDefaultChk.checked and (urlCmd = '~nodefault') then urlCmd:=''; @@ -5465,7 +5456,7 @@ var end; // provide access to any [section] in the tpl, included [progress] - if data.urlvars.values['mode'] = 'section' then + if mode = 'section' then s:=first(data.urlvars.values['id'], 'no-id') // no way, you must specify the id else if (f = rootFile) and (urlCmd > '') then s:=substr(urlCmd,2) @@ -5500,7 +5491,7 @@ var end; if (urlCmd = '~folder.tar') - or (data.urlvars.values['mode'] = 'archive') then + or (mode = 'archive') then begin serveTar(); exit; @@ -8963,7 +8954,7 @@ if quitting then exit; if item = NIL then exit; data:=conn2data(item); if data = NIL then exit; -item.caption:=nonEmptyConcat('', data.usr, '@')+data.address+':'+data.conn.port; +item.caption:=nonEmptyConcat('', data.user, '@')+data.address+':'+data.conn.port; while item.subitems.count < 5 do item.subitems.add(''); @@ -9477,7 +9468,7 @@ while not tlv.isOver() do FK_USERPWD: begin data:=base64decode(data); - f.user:=chop(pos(':',data),1,data); + f.user:=chop(':',data); f.pwd:=data; usersInVFS.track(f.user, f.pwd); end; diff --git a/optionsDlg.dfm b/optionsDlg.dfm index cc82d68..01d5c82 100644 --- a/optionsDlg.dfm +++ b/optionsDlg.dfm @@ -119,6 +119,10 @@ object optionsFrm: ToptionsFrm object accountsPage: TTabSheet Caption = 'Accounts' ImageIndex = 29 + ExplicitLeft = 0 + ExplicitTop = 0 + ExplicitWidth = 0 + ExplicitHeight = 0 DesignSize = ( 797 385) diff --git a/scriptLib.pas b/scriptLib.pas index 99b6bcc..3a43792 100644 --- a/scriptLib.pas +++ b/scriptLib.pas @@ -1628,7 +1628,7 @@ var if assigned(md.cd) then begin - usr:=md.cd.usr; + usr:=md.cd.user; if name = '%host%' then result:=getSafeHost(md.cd) else if name = '%ip%' then @@ -2467,7 +2467,7 @@ try if mainfrm.macrosLogChk.checked then begin if not fileExists(MACROS_LOG_FILE) then - saveFile(MACROS_LOG_FILE, HEADER); + saveTextFile(MACROS_LOG_FILE, HEADER); macrosLog(fullMacro, result, md.logTS); md.logTS:=FALSE; end; diff --git a/utillib.pas b/utillib.pas index 4a7e556..6136077 100644 --- a/utillib.pas +++ b/utillib.pas @@ -2993,6 +2993,8 @@ var i: integer; begin result:=NIL; +if user = '' then + exit; for i:=0 to length(accounts)-1 do if sameText(user, accounts[i].user) then begin