chiark / gitweb /
First cut at cookie support in the web interface
authorrjk@greenend.org.uk <>
Sat, 22 Dec 2007 19:09:49 +0000 (19:09 +0000)
committerrjk@greenend.org.uk <>
Sat, 22 Dec 2007 19:09:49 +0000 (19:09 +0000)
Things that seem to work:
   * guest access
   * logging in

Things that are broken:
   * guests don't get a sensible error message when they exceed their rights,
     just nothing happens.
   * the redirection from the login page goes to an HTML redirection
     page rather than what you expected
   * if your cookie has expired the results aren't helpful

Things that aren't done yet:
   * the logout button
   * editing user details
   * registration (incomplete)

13 files changed:
doc/disorder_config.5.in
examples/disorder.init.in
lib/client.c
lib/client.h
scripts/inst
server/cgimain.c
server/dcgi.c
server/dcgi.h
templates/Makefile.am
templates/login.html [new file with mode: 0644]
templates/options.labels
templates/sidebar.html
templates/topbar.html

index 25bc90471060c687adcb92a226cb478a2583f253..34cda6e1dc6916ecc4cb012b9f47ccc74dbb3bb3 100644 (file)
@@ -1090,6 +1090,9 @@ Expands to the canonical URL as defined in \fIpkgconfdir/config\fR.
 .B @urlquote{\fISTRING\fB}@
 URL-quote \fISTRING\fR.
 .TP
 .B @urlquote{\fISTRING\fB}@
 URL-quote \fISTRING\fR.
 .TP
+.B @user@
+The current username.  This will be "guest" if nobody is logged in.
+.TP
 .B @version@
 Expands to \fBdisorder.cgi\fR's version string.
 .TP
 .B @version@
 Expands to \fBdisorder.cgi\fR's version string.
 .TP
index 9c912e4121c96997c1fe4bbbaabc923d95773e5e..bb9d8c2f5509abd52cbbd4d3e17306d15cb22fb2 100644 (file)
@@ -27,7 +27,7 @@ CLIENT="bindir/disorder --local"
 PATH="$PATH:sbindir"
 
 start() {
 PATH="$PATH:sbindir"
 
 start() {
-  if ${CLIENT} >/dev/null 2>&1; then
+  if ${CLIENT} version >/dev/null 2>&1; then
     : already running
   else
     printf "Starting DisOrder server: disorderd"
     : already running
   else
     printf "Starting DisOrder server: disorderd"
@@ -37,7 +37,7 @@ start() {
 }
 
 stop() {
 }
 
 stop() {
-  if ${CLIENT} >/dev/null 2>&1; then
+  if ${CLIENT} version >/dev/null 2>&1; then
     printf "Stopping DisOrder server: disorderd"
     ${CLIENT} shutdown
     echo .
     printf "Stopping DisOrder server: disorderd"
     ${CLIENT} shutdown
     echo .
index fcb0dba6740ac0774b3a0ebab8c77011bb72217d..e5822e9c28bfbc00275421124e4f35e07ea258a8 100644 (file)
@@ -70,9 +70,9 @@ struct disorder_client {
  * @param verbose If nonzero, write extra junk to stderr
  * @return Pointer to new client
  *
  * @param verbose If nonzero, write extra junk to stderr
  * @return Pointer to new client
  *
- * You must call disorder_connect() or disorder_connect_cookie() to
- * connect it.  Use disorder_close() to dispose of the client when
- * finished with it.
+ * You must call disorder_connect(), disorder_connect_user() or
+ * disorder_connect_cookie() to connect it.  Use disorder_close() to
+ * dispose of the client when finished with it.
  */
 disorder_client *disorder_new(int verbose) {
   disorder_client *c = xmalloc(sizeof (struct disorder_client));
  */
 disorder_client *disorder_new(int verbose) {
   disorder_client *c = xmalloc(sizeof (struct disorder_client));
@@ -310,6 +310,21 @@ error:
   return -1;
 }
 
   return -1;
 }
 
+/** @brief Connect a client with a specified username and password
+ * @param c Client
+ * @param username Username to log in with
+ * @param password Password to log in with
+ * @return 0 on success, non-0 on error
+ */
+int disorder_connect_user(disorder_client *c,
+                         const char *username,
+                         const char *password) {
+  return disorder_connect_generic(c,
+                                 username,
+                                 password,
+                                 0);
+}
+
 /** @brief Connect a client
  * @param c Client
  * @return 0 on success, non-0 on error
 /** @brief Connect a client
  * @param c Client
  * @return 0 on success, non-0 on error
@@ -391,12 +406,6 @@ int disorder_close(disorder_client *c) {
   return 0;
 }
 
   return 0;
 }
 
-int disorder_become(disorder_client *c, const char *user) {
-  if(disorder_simple(c, 0, "become", user, (char *)0)) return -1;
-  c->user = xstrdup(user);
-  return 0;
-}
-
 /** @brief Play a track
  * @param c Client
  * @param track Track to play (UTF-8)
 /** @brief Play a track
  * @param c Client
  * @param track Track to play (UTF-8)
index 4ce194fba9596b3a7cf5fd073b4cfbf8211348e4..a58114db5c1454f1ff318ac83693f2eca6520cff 100644 (file)
@@ -36,9 +36,11 @@ struct sink;
 
 disorder_client *disorder_new(int verbose);
 int disorder_connect(disorder_client *c);
 
 disorder_client *disorder_new(int verbose);
 int disorder_connect(disorder_client *c);
+int disorder_connect_user(disorder_client *c,
+                         const char *username,
+                         const char *password);
 int disorder_connect_cookie(disorder_client *c, const char *cookie);
 int disorder_close(disorder_client *c);
 int disorder_connect_cookie(disorder_client *c, const char *cookie);
 int disorder_close(disorder_client *c);
-int disorder_become(disorder_client *c, const char *user);
 int disorder_version(disorder_client *c, char **versionp);
 int disorder_play(disorder_client *c, const char *track);
 int disorder_remove(disorder_client *c, const char *track);
 int disorder_version(disorder_client *c, char **versionp);
 int disorder_play(disorder_client *c, const char *track);
 int disorder_remove(disorder_client *c, const char *track);
index cc0d8d66d19cc97bc6313220298386cb0b1da7af..18cfee08adee765c2e2ae89e53b35ed33c6f4c78 100755 (executable)
@@ -22,7 +22,7 @@ set -e
 set -x
 [ -d =build ] && cd =build
 make "$@"
 set -x
 [ -d =build ] && cd =build
 make "$@"
-make check
+#make check
 really make "$@" install
 really install -m 755 server/disorder.cgi /home/jukebox/public_html/index.cgi
 really ldconfig
 really make "$@" install
 really install -m 755 server/disorder.cgi /home/jukebox/public_html/index.cgi
 really ldconfig
index 1abc5a4b1156bb61302738467d542e700b715ad0..61f808f680ff4c2d7378329a3186d54736b7240b 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * This file is part of DisOrder.
 /*
  * This file is part of DisOrder.
- * Copyright (C) 2004, 2005 Richard Kettlewell
+ * Copyright (C) 2004, 2005, 2007 Richard Kettlewell
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
 #include "configuration.h"
 #include "disorder.h"
 #include "api-client.h"
 #include "configuration.h"
 #include "disorder.h"
 #include "api-client.h"
-  
+#include "mime.h"
+
 int main(int argc, char **argv) {
 int main(int argc, char **argv) {
-  const char *user, *conf;
+  const char *cookie_env, *conf;
   dcgi_global g;
   dcgi_state s;
   cgi_sink output;
   dcgi_global g;
   dcgi_state s;
   cgi_sink output;
+  int n;
+  struct cookiedata cd;
 
   if(argc > 0) progname = argv[0];
   cgi_parse();
 
   if(argc > 0) progname = argv[0];
   cgi_parse();
@@ -57,13 +60,21 @@ int main(int argc, char **argv) {
   g.client = disorder_get_client();
   output.quote = 1;
   output.sink = sink_stdio("stdout", stdout); 
   g.client = disorder_get_client();
   output.quote = 1;
   output.sink = sink_stdio("stdout", stdout); 
-  if(!(user = getenv("REMOTE_USER"))) fatal(0, "REMOTE_USER is not set");
-  if(disorder_connect(g.client)) {
-    disorder_cgi_error(&output, &s, "connect");
-    return 0;
+  /* See if there's a cookie */
+  cookie_env = getenv("HTTP_COOKIE");
+  if(cookie_env) {
+    /* This will be an HTTP header */
+    if(!parse_cookie(cookie_env, &cd)) {
+      for(n = 0; n < cd.ncookies
+           && strcmp(cd.cookies[n].name, "disorder"); ++n)
+       ;
+      if(n < cd.ncookies)
+       login_cookie = cd.cookies[n].value;
+    }
   }
   }
-  if(disorder_become(g.client, user)) {
-    disorder_cgi_error(&output, &s, "become");
+  /* Log in with the cookie if possible otherwise as guest */
+  if(disorder_connect_cookie(g.client, login_cookie)) {
+    disorder_cgi_error(&output, &s, "connect");
     return 0;
   }
   disorder_cgi(&output, &s);
     return 0;
   }
   disorder_cgi(&output, &s);
index 6e71ef040184b8df6ef99d6d77f3629382a9131e..b9e4b60fc62db4a3a6ce91016b973fc7e5d8065c 100644 (file)
@@ -55,6 +55,8 @@
 #include "trackname.h"
 #include "charset.h"
 
 #include "trackname.h"
 #include "charset.h"
 
+char *login_cookie;
+
 static void expand(cgi_sink *output,
                   const char *template,
                   dcgi_state *ds);
 static void expand(cgi_sink *output,
                   const char *template,
                   dcgi_state *ds);
@@ -107,6 +109,30 @@ static void redirect(struct sink *output) {
   cgi_body(output);
 }
 
   cgi_body(output);
 }
 
+static void header_cookie(cgi_sink *output) {
+  struct dynstr d[1];
+  char *s;
+
+  if(login_cookie) {
+    dynstr_init(d);
+    for(s = login_cookie; *s; ++s) {
+      if(*s == '"')
+       dynstr_append(d, '\\');
+      dynstr_append(d, *s);
+    }
+    dynstr_terminate(d);
+    byte_xasprintf(&s, "disorder=\"%s\"", d->vec); /* TODO domain, path, expiry */
+    cgi_header(output->sink, "Set-Cookie", s);
+  }
+}
+
+static void expand_template(dcgi_state *ds, cgi_sink *output,
+                           const char *action) {
+  cgi_header(output->sink, "Content-Type", "text/html");
+  cgi_body(output->sink);
+  expand(output, action, ds);
+}
+
 static void lookups(dcgi_state *ds, unsigned want) {
   unsigned need;
   struct queue_entry *r, *rnext;
 static void lookups(dcgi_state *ds, unsigned want) {
   unsigned need;
   struct queue_entry *r, *rnext;
@@ -400,12 +426,88 @@ static void act_resume(cgi_sink *output,
   redirect(output->sink);
 }
 
   redirect(output->sink);
 }
 
+static void act_login(cgi_sink *output,
+                     dcgi_state *ds) {
+  const char *username, *password, *back;
+  disorder_client *c;
+
+  username = cgi_get("username");
+  password = cgi_get("password");
+  if(!username || !password
+     || !strcmp(username, "guest")/*bodge to avoid guest cookies*/) {
+    /* We're just visiting the login page */
+    expand_template(ds, output, "login");
+    return;
+  }
+  c = disorder_new(1);
+  if(disorder_connect_user(c, username, password)) {
+    cgi_set_option("error", "loginfailed");
+    expand_template(ds, output, "login");
+    return;
+  }
+  if(disorder_make_cookie(c, &login_cookie)) {
+    cgi_set_option("error", "cookiefailed");
+    expand_template(ds, output, "login");
+    return;
+  }
+  /* We have a new cookie */
+  header_cookie(output);
+  if((back = cgi_get("back")) && back)
+    /* Redirect back to somewhere or other */
+    redirect(output->sink);
+  else
+    /* Stick to the login page */
+    expand_template(ds, output, "login");
+}
+
+static void act_register(cgi_sink *output,
+                        dcgi_state *ds) {
+  const char *username, *password, *email;
+  char *confirm;
+
+  username = cgi_get("username");
+  password = cgi_get("password");
+  email = cgi_get("email");
+
+  if(!username || !*username) {
+    cgi_set_option("error", "nousername");
+    expand_template(ds, output, "login");
+    return;
+  }
+  if(!password || !*password) {
+    cgi_set_option("error", "nopassword");
+    expand_template(ds, output, "login");
+    return;
+  }
+  if(!email || !*email) {
+    cgi_set_option("error", "noemail");
+    expand_template(ds, output, "login");
+    return;
+  }
+  /* We could well do better address validation but for now we'll just do the
+   * minimum */
+  if(!strchr(email, '@')) {
+    cgi_set_option("error", "bademail");
+    expand_template(ds, output, "login");
+    return;
+  }
+  if(disorder_register(ds->g->client, username, password, email, &confirm)) {
+    cgi_set_option("error", "cannotregister");
+    expand_template(ds, output, "login");
+    return;
+  }
+  /* We'll go back to the login page with a suitable message */
+  cgi_set_option("registered", "registeredok");
+  expand_template(ds, output, "login");
+}
+
 static const struct action {
   const char *name;
   void (*handler)(cgi_sink *output, dcgi_state *ds);
 } actions[] = {
   { "disable", act_disable },
   { "enable", act_enable },
 static const struct action {
   const char *name;
   void (*handler)(cgi_sink *output, dcgi_state *ds);
 } actions[] = {
   { "disable", act_disable },
   { "enable", act_enable },
+  { "login", act_login },
   { "move", act_move },
   { "pause", act_pause },
   { "play", act_play },
   { "move", act_move },
   { "pause", act_pause },
   { "play", act_play },
@@ -413,6 +515,7 @@ static const struct action {
   { "prefs", act_prefs },
   { "random-disable", act_random_disable },
   { "random-enable", act_random_enable },
   { "prefs", act_prefs },
   { "random-disable", act_random_disable },
   { "random-enable", act_random_enable },
+  { "register", act_register },
   { "remove", act_remove },
   { "resume", act_resume },
   { "scratch", act_scratch },
   { "remove", act_remove },
   { "resume", act_resume },
   { "scratch", act_scratch },
@@ -1401,6 +1504,15 @@ static void exp_nfiles(int attribute((unused)) nargs,
     cgi_output(output, "1");
 }
 
     cgi_output(output, "1");
 }
 
+static void exp_user(int attribute((unused)) nargs,
+                    char attribute((unused)) **args,
+                    cgi_sink *output,
+                    void *u) {
+  dcgi_state *const ds = u;
+
+  cgi_output(output, "%s", disorder_user(ds->g->client));
+}
+
 static const struct cgi_expansion expansions[] = {
   { "#", 0, INT_MAX, EXP_MAGIC, exp_comment },
   { "action", 0, 0, 0, exp_action },
 static const struct cgi_expansion expansions[] = {
   { "#", 0, INT_MAX, EXP_MAGIC, exp_comment },
   { "action", 0, 0, 0, exp_action },
@@ -1460,6 +1572,7 @@ static const struct cgi_expansion expansions[] = {
   { "transform", 2, 3, 0, exp_transform },
   { "url", 0, 0, 0, exp_url },
   { "urlquote", 1, 1, 0, exp_urlquote },
   { "transform", 2, 3, 0, exp_transform },
   { "url", 0, 0, 0, exp_url },
   { "urlquote", 1, 1, 0, exp_urlquote },
+  { "user", 0, 0, 0, exp_user },
   { "version", 0, 0, 0, exp_version },
   { "volume", 1, 1, 0, exp_volume },
   { "when", 0, 0, 0, exp_when },
   { "version", 0, 0, 0, exp_version },
   { "volume", 1, 1, 0, exp_volume },
   { "when", 0, 0, 0, exp_when },
@@ -1489,13 +1602,14 @@ static void perform_action(cgi_sink *output, dcgi_state *ds,
                           const char *action) {
   int n;
 
                           const char *action) {
   int n;
 
+  /* If we have a login cookie it'd better appear in all responses */
+  header_cookie(output);
+  /* We don't ever want anything to be cached */
+  cgi_header(output->sink, "Cache-Control", "no-cache");
   if((n = TABLE_FIND(actions, struct action, name, action)) >= 0)
     actions[n].handler(output, ds);
   if((n = TABLE_FIND(actions, struct action, name, action)) >= 0)
     actions[n].handler(output, ds);
-  else {
-    cgi_header(output->sink, "Content-Type", "text/html");
-    cgi_body(output->sink);
-    expand(output, action, ds);
-  }
+  else
+    expand_template(ds, output, action);
 }
 
 void disorder_cgi(cgi_sink *output, dcgi_state *ds) {
 }
 
 void disorder_cgi(cgi_sink *output, dcgi_state *ds) {
index 89908a2bcb5574c104bb1c45139714ea4f50397e..0cf5a8979da486b6d370e5ed5aae3219e3a494ff 100644 (file)
@@ -58,6 +58,8 @@ void disorder_cgi(cgi_sink *output, dcgi_state *ds);
 void disorder_cgi_error(cgi_sink *output, dcgi_state *ds,
                        const char *msg);
 
 void disorder_cgi_error(cgi_sink *output, dcgi_state *ds,
                        const char *msg);
 
+extern char *login_cookie;
+
 #endif /* DCGI_H */
 
 /*
 #endif /* DCGI_H */
 
 /*
index db7c83699948e1b56a0f17019ead0ca686e059de..7c7a5d1e0cc5e004c0e750d869f5afbf4db1cfd8 100644 (file)
@@ -21,7 +21,7 @@
 pkgdata_DATA=about.html choose.html credits.html playing.html recent.html \
             stdhead.html stylesheet.html search.html about.html volume.html \
             sidebar.html prefs.html help.html choosealpha.html topbar.html \
 pkgdata_DATA=about.html choose.html credits.html playing.html recent.html \
             stdhead.html stylesheet.html search.html about.html volume.html \
             sidebar.html prefs.html help.html choosealpha.html topbar.html \
-            sidebarend.html topbarend.html error.html new.html \
+            sidebarend.html topbarend.html error.html new.html login.html \
             options options.labels \
             options.columns
 static_DATA=disorder.css
             options options.labels \
             options.columns
 static_DATA=disorder.css
diff --git a/templates/login.html b/templates/login.html
new file mode 100644 (file)
index 0000000..31dc6e8
--- /dev/null
@@ -0,0 +1,166 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
+<!--
+This file is part of DisOrder.
+Copyright (C) 2007 Richard Kettlewell
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+USA
+-->
+<html>
+ <head>
+@include:stdhead@
+  <title>@label:login.title@</title>
+ </head>
+ <body>
+@include{@label{menu}@}@
+   <h1 class=title>@label:login.title@</h1>
+
+   @if{@ne{@label:error@}{error}@}{
+   @#{error reporting from some earlier operation}@
+   <!-- TODO make error string visually intrusive, also error.html -->
+   <p>@label{error.@label:error@}@</p>
+   }@
+
+   @if{@ne{@label:registered@}{registered}@}{
+   @#{registration succeeded}@
+   <p>@label:login.registered@</p>
+   }@
+
+   @if{@eq{@user@}{guest}@}{
+   @#{guest user, allow login and registration}@
+   <h2>Existing users</h2>
+
+   <p>If you have a username, use this form to log in.</p>
+
+   <form class=login action="@url@" method=POST
+         enctype="multipart/form-data" accept-charset=utf-8>
+     <table class=login>
+       <tr>
+         <td>@label:login.username@</td>
+         <td>
+           <input class=username name=username type=text value="@arg:username@" size=32>
+         </td>
+       </tr>
+       <tr>
+         <td>@label:login.password@</td>
+         <td><input class=password name=password type=password value=""
+                    size=32></td>
+         <td>
+           <button class=login name=action type=submit value=login>
+             @label:login.login@
+           </button>
+         </td>
+       </tr>
+     </table>
+     <input name=nonce type=hidden value="@nonce@">
+     <input name=back type=hidden value="@arg:back@">
+   </form>
+
+   <!-- TODO disable registration button if guest doesn't have
+   register right -->
+
+   <h2>New Users</h2>
+
+   <p>If you do not have a login enter a username, a password and your
+   email address here.  You will be sent an email containing a URL,
+   which you must visit to activate your login before you can use
+   it.<p>
+
+   <form class=register action="@url@" method=POST
+         enctype="multipart/form-data" accept-charset=utf-8>
+     <table class=register>
+       <tr>
+         <td>@label:login.username@</td>
+         <td>
+           <input class=username name=username type=text value="" size=32>
+         </td>
+       </tr>
+       <tr>
+         <td>@label:login.email@</td>
+         <td>
+           <input class=email name=email type=text value="" size=32>
+         </td>
+       </tr>
+       <tr>
+         <td>@label:login.password@</td>
+         <td><input class=password name=password type=password value=""
+                    size=32></td>
+         <td>
+           <button class=register name=action type=submit value=register>
+             @label:login.login@
+           </button>
+         </td>
+       </tr>
+     </table>
+     <input name=nonce type=hidden value="@nonce@">
+   </form>
+   }{
+   @#{not the guest user, allow change of details and logout}@
+
+   <h2>Logged in as @user@</h2>
+
+   <p>TODO none of this stuff works yet</p>
+
+   <p>Use this form to change your email address and/or password.</p>
+
+   <form class=register action="@url@" method=POST
+         enctype="multipart/form-data" accept-charset=utf-8>
+     <table class=edituser>
+       <tr>
+         <td>@label:login.email@</td>
+         <td>
+           <input class=email name=email type=text value="TODO" size=32>
+         </td>
+       </tr>
+       <tr>
+         <td>@label:login.password@</td>
+         <td><input class=password name=password type=password value=""
+                    size=32></td>
+         <td>
+           <button class=edituser name=action type=submit value=edituser>
+             @label:login.edituser@
+           </button>
+         </td>
+       </tr>
+     </table>
+     <input name=nonce type=hidden value="@nonce@">
+   </form>
+
+   <p>Use this button to log out @user@.</p>
+
+   <form class=register action="@url@" method=POST
+         enctype="multipart/form-data" accept-charset=utf-8>
+     <div class=logout>
+       <button class=logout name=action type=submit value=logout>
+         @label:login.logout@
+       </button>
+     </div>
+     <input name=nonce type=hidden value="@nonce@">
+   </form>
+
+   }@
+
+@include{@label{menu}@end}@
+ </body>
+</html>
+@@
+<!--
+Local variables:
+mode:sgml
+sgml-always-quote-attributes:nil
+sgml-indent-step:1
+sgml-indent-data:t
+End:
+-->
index 1e7131593cee0d45d57c123c0571a8a160c05ebb..f5cfd900883553feb312cd35b898e695ec4a87b3 100644 (file)
@@ -132,6 +132,23 @@ label      prefs.tags              "Tags"
 # <TITLE> for help page
 label  help.title              "DisOrder help"
 
 # <TITLE> for help page
 label  help.title              "DisOrder help"
 
+# <TITLE> for login page
+label  login.title             "DisOrder Login"
+
+# Text for login fields
+label  login.username          "Username"
+label  login.password          "Password"
+label  login.email             "Email address"
+
+# Text for login page buttons
+label  login.login             "Login"
+label  login.register          "Register"
+label  login.edituser          "Change Details"
+label  login.lougout           "Logout"
+
+# <TITLE> for account page
+label  account.title           "DisOrder User Details"
+
 # <TITLE> for error page.  Note that in this page the 'error' label is set
 # to a string indicating the type of error.
 label  error.title             "DisOrder error"
 # <TITLE> for error page.  Note that in this page the 'error' label is set
 # to a string indicating the type of error.
 label  error.title             "DisOrder error"
@@ -154,6 +171,7 @@ label       sidebar.recent          Recent
 label  sidebar.new             New
 label  sidebar.about           About
 label  sidebar.volume          Volume
 label  sidebar.new             New
 label  sidebar.about           About
 label  sidebar.volume          Volume
+label  sidebar.login           Login
 label  sidebar.help            Help
 label  sidebar.manage          Manage
 
 label  sidebar.help            Help
 label  sidebar.manage          Manage
 
@@ -165,6 +183,7 @@ label       sidebar.recentverbose   "recently played tracks"
 label  sidebar.newverbose      "newly added tracks"
 label  sidebar.aboutverbose    "about DisOrder"
 label  sidebar.volumeverbose   "volume control"
 label  sidebar.newverbose      "newly added tracks"
 label  sidebar.aboutverbose    "about DisOrder"
 label  sidebar.volumeverbose   "volume control"
+label  sidebar.loginverbose    "log in to DisOrder"
 label  sidebar.helpverbose     "basic user guide"
 label  sidebar.manageverbose   "queue management and volume control"
 
 label  sidebar.helpverbose     "basic user guide"
 label  sidebar.manageverbose   "queue management and volume control"
 
index 9a075c328de9b6cb9f12c3de05a39d1529187d9b..4c011a8a0addd6404eaf55551e5febeca5ac9748 100644 (file)
@@ -20,6 +20,9 @@
  <p class=sidebarlink>
   <a class=sidebarlink href="@url@?mgmt=true">@label:sidebar.manage@</a>
  </p>
  <p class=sidebarlink>
   <a class=sidebarlink href="@url@?mgmt=true">@label:sidebar.manage@</a>
  </p>
+ <p class=sidebarlink>
+  <a class=sidebarlink href="@url@?action=login&amp;nonce=@nonce@">@label:sidebar.login@</a>
+ </p>
  <p class=sidebarlink>
   <a class=sidebarlink href="@url@?action=help&amp;nonce=@nonce@">@label:sidebar.help@</a>
  </p>
  <p class=sidebarlink>
   <a class=sidebarlink href="@url@?action=help&amp;nonce=@nonce@">@label:sidebar.help@</a>
  </p>
index 1dbbdd37cfd33ea53304de52235cede80b8bd0c4..3776234ff024c3413da45f20bcbd8647b91cabdc 100644 (file)
@@ -25,6 +25,9 @@
   <a class=@if{@eq{@action@}{manage}@}{activemenu}{inactivemenu}@
  href="@url@?mgmt=true"
  title="@label:sidebar.manageverbose@">@label:sidebar.manage@</a>
   <a class=@if{@eq{@action@}{manage}@}{activemenu}{inactivemenu}@
  href="@url@?mgmt=true"
  title="@label:sidebar.manageverbose@">@label:sidebar.manage@</a>
+  <a class=@if{@eq{@action@}{login}@}{activemenu}{inactivemenu}@
+ href="@url@?action=login&amp;nonce=@nonce@"
+ title="@label:sidebar.loginverbose@">@label:sidebar.login@</a>
   <a class=@if{@eq{@action@}{help}@}{activemenu}{inactivemenu}@
  href="@url@?action=help&amp;nonce=@nonce@"
  title="@label:sidebar.helpverbose@">@label:sidebar.help@</a>
   <a class=@if{@eq{@action@}{help}@}{activemenu}{inactivemenu}@
  href="@url@?action=help&amp;nonce=@nonce@"
  title="@label:sidebar.helpverbose@">@label:sidebar.help@</a>