FormsAuth.java

/***************************************************************************
   Copyright 2013 Emily Estes

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
***************************************************************************/
package net.metanotion.formsauth;


import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import javax.inject.Named;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import net.metanotion.authident.UserToken;
import net.metanotion.io.ClassLoaderFileSystem;
import net.metanotion.io.File;
import net.metanotion.io.FileSystem;
import net.metanotion.scripting.ObjectServer;
import net.metanotion.scripting.ObjectServerTree;
import net.metanotion.simpletemplate.ResourceFactory;
import net.metanotion.simpletemplate.TemplateResources;
import net.metanotion.util.Dispatcher;
import net.metanotion.util.MapToListDispatcher;
import net.metanotion.util.NamedParameterMapper;
import net.metanotion.util.Nullable;
import net.metanotion.util.SecureString;
import net.metanotion.util.Service;
import net.metanotion.util.ServiceListDispatcherMixin;
import net.metanotion.util.StateMachine;
import net.metanotion.util.ReflectionListDispatcher;
import net.metanotion.util.Unknown;
import net.metanotion.util.Wrap;
import net.metanotion.web.HttpMethod;
import net.metanotion.web.HttpStatus;
import net.metanotion.web.HttpValues;
import net.metanotion.web.RequestObject;
import net.metanotion.web.concrete.BasicRequestObject;
import net.metanotion.web.concrete.DelegatingRequestObject;
import net.metanotion.web.concrete.HttpUtil;
import net.metanotion.web.concrete.RequestObjectParamDispatcherMixin;
import net.metanotion.web.concrete.WebInterfaceDispatcher;

/** Templating Library for scripting auth forms into pages. */
public final class FormsAuth {
	private static final Logger logger = LoggerFactory.getLogger(FormsAuth.class);

	private static final String FORMSAUTH_SCRIPTABLE_OBJECT = "FormsAuth";

	private static final String SLASH = "/";

	private static final String ACCOUNT_CREATED_PAGE = "accountCreatedPage.html";
	private static final String ADD_ACCOUNT_FAILED_PAGE = "addAccountFailedPage.html";
	private static final String CHANGE_PASSWORD_FAILED_PAGE = "changePasswordFailedPage.html";
	private static final String CREATE_ACCOUNT_FAILED_PAGE = "createAccountFailedPage.html";
	private static final String CREATE_ACCOUNT_PAGE = "createAccountPage.html";
	private static final String EDIT_ACCOUNT_PAGE = "editAccountPage.html";
	private static final String EMAIL_VALIDATED_PAGE = "emailValidatedPage.html";
	private static final String EMAIL_VALIDATION_SENT_PAGE = "emailValidationSentPage.html";
	private static final String LOGGED_OUT_PAGE = "loggedOutPage.html";
	private static final String LOGIN_FAILED_PAGE = "loginFailedPage.html";
	private static final String LOGIN_PAGE = "loginPage.html";
	private static final String PASSWORD_RESET_PAGE = "passwordResetPage.html";
	private static final String PASSWORD_RESET_REQUESTED_PAGE = "passwordResetRequestedPage.html";
	private static final String REMOVE_ACCOUNT_FAILED_PAGE = "removeAccountFailedPage.html";
	private static final String REQUEST_PASSWORD_RESET_PAGE = "requestPasswordResetPage.html";
	private static final String VALIDATION_FAILED_PAGE = "validationFailedPage.html";

	private static final String ACCOUNT_LIST_ITEM_FORM = "/forms/accountListItem.html";
	private static final String ACCOUNT_PANEL_ANONYMOUS_FORM = "/forms/accountPanelAnonymousForm.html";
	private static final String ACCOUNT_PANEL_LOGGED_IN_FORM = "/forms/accountPanelLoggedInForm.html";
	private static final String ADD_ACCOUNT_FORM = "/forms/addAccountForm.html";
	private static final String CHANGE_PASSWORD_FORM = "/forms/changePasswordForm.html";
	private static final String CREATE_ACCOUNT_FORM = "/forms/createAccountForm.html";
	private static final String LOGIN_FORM = "/forms/loginForm.html";
	private static final String LOGOUT_FORM = "/forms/logoutForm.html";
	private static final String REMOVE_ACCOUNT_FORM = "/forms/removeAccountForm.html";
	private static final String REQUEST_EMAIL_VALIDATION_FORM = "/forms/requestEmailValidationForm.html";
	private static final String REQUEST_PASSWORD_RESET_FORM = "/forms/requestPasswordResetForm.html";

	private static final String CURRENT_PAGE_VAR = "currentPage";
	private static final String EMAIL_ID_VAR = "emailID";
	private static final String OS_VAR = "os";
	private static final String RESET_ID_VAR = "rid";
	private static final String RO_VAR = "ro";
	private static final String UT_VAR = "ut";
	private static final String TOKEN_VAR = "token";

	private interface AuthUI {
		public String currentPage(@Named (CURRENT_PAGE_VAR) String currentPage);
		public String emailID(@Named(EMAIL_ID_VAR) String emailID);
		public String resetID(@Named(RESET_ID_VAR) String resetID);
		public String token(@Named(TOKEN_VAR) String token);

		public String loginAction();
		public String logoutAction();
		public String requestPasswordResetAction();
		public String resetPasswordAction();
		public String createAccountAction();
		public String addAccountAction();
		public String removeAccountAction();
		public String changePasswordAction();
		public String sendAccountValidationAction();
		public String createAccountURI();
		public String editAccountURI();
		public String requestPasswordResetURI();
		public Object loginForm(@Named (CURRENT_PAGE_VAR) String currentPage);
		public Object logoutForm(@Named (CURRENT_PAGE_VAR) String currentPage);
		public Object removeAccountForm(@Named(EMAIL_ID_VAR) String emailID);
		public Object requestEmailValidationForm(@Named(EMAIL_ID_VAR) String emailID);
	}

	private static final Dispatcher fsoDisp;
	private static final Dispatcher authDisp;
	private static final Dispatcher accountUIDisp;
	static {
		try {
			authDisp = MapToListDispatcher.getDispatcher(AuthUI.class);
			final Map<String,Iterable<String>> paramMap = new NamedParameterMapper().read(FormScriptingObject.class);
			fsoDisp = new RequestObjectParamDispatcherMixin<Unknown>(FormScriptingObject.class, paramMap,
				new MapToListDispatcher<Unknown>(paramMap,
					new ServiceListDispatcherMixin<FormScriptingObject>(FormScriptingObject.class,
							new ReflectionListDispatcher<FormScriptingObject>(FormScriptingObject.class))));
			accountUIDisp = new WebInterfaceDispatcher<FormsAuthAPI>(FormsAuthAPI.class);
		} catch (IOException ioe) { throw new RuntimeException(ioe); }
	}

	/** Return a suitable dispatcher for handling {@link net.metanotion.formsauth.FormsAuthAPI} HTTP Requests. This
		method is static because it requires no information/configuration.
		@return A dispatcher suitable for any web application.
	*/
	public static Dispatcher<? extends Object,RequestObject> dispatcher() { return accountUIDisp; }

	/** Since the url's for the standard email validation and reset passward pages are known, we can construct an
	instance of the AuthMailer.Urls data structure given a baseUrl, this utility method does that.
		@param baseUrl The base url for the forms authentication UI.
		@return An AuthMailer.Urls data structure with the appropriate validation and reset password URL's.
	*/
	public static AuthMailer.Urls standardUrls(final String baseUrl) {
		return new AuthMailer.Urls(baseUrl + "validateAccountPage", baseUrl + "resetPasswordPage.html");
	}

	/** This is the file system referencing the default templates used to render the UI for account management
		operations and to display authentication results. You can make a resource factory for customized templates that
		falls through to a {@link net.metanotion.simpletemplate.ResourceFactory} using this file system for the rest of
		the templates that you haven't customized. Or you can provide all the templates you need and ignore this.
	*/
	public static final FileSystem<File> DEFAULT_TEMPLATE_FS =
		new ClassLoaderFileSystem(FormsAuth.class.getClassLoader(), "/net/metanotion/formsauth/htmltemplates");

	private final AuthUI authTemplateObj = new AuthForms();
	private final ObjectServer authOS = new UIObjectServer();
	private final FormScriptingObject fso = new FormScriptingObject();
	private final FormsAuthAPI ui = new ServerFormsAuth();

	private final String prefix;
	private final AuthAPI authAPI;
	private final ResourceFactory templates;

	/** Create an HTML Forms Server Authentication/account management UI object using the default templates packaged
		in the .jar file(see {@link #DEFAULT_TEMPLATE_FS}).
		@param prefix The prefix prepended to all URI's directed at this object.
		@param authAPI An authentication implementation this class will delegate the actual authentication and account
			management functionality to.
	*/
	public FormsAuth(final String prefix, final AuthAPI authAPI) {
		this(prefix, authAPI, new TemplateResources(DEFAULT_TEMPLATE_FS));
	}

	/** Create an HTML Forms Server Authentication/account management UI object.
		@param prefix The prefix prepended to all URI's directed at this object.
		@param authAPI An authentication implementation this class will delegate the actual authentication and account
			management functionality to.
		@param fs The file system to provide templates handled by the
			{@link net.metanotion.simpletemplate.TemplateResources} scripting engine to render the UI for this class.
	*/
	public FormsAuth(final String prefix, final AuthAPI authAPI, final FileSystem<? extends File> fs) {
		this(prefix, authAPI, new TemplateResources(fs));
	}

	/** Create an HTML Forms Server Authentication/account management UI object.
		@param prefix The prefix prepended to all URI's directed at this object.
		@param authAPI An authentication implementation this class will delegate the actual authentication and account
			management functionality to.
		@param templates The resource factory that will provide the scriptable templates to render the UI for this
			class.
	*/
	public FormsAuth(final String prefix, final AuthAPI authAPI, final ResourceFactory templates) {
		this.prefix = prefix;
		this.authAPI = authAPI;
		this.templates = templates;
	}

	private final class UIObjectServer implements ObjectServer {
		@Override public Dispatcher dispatcher(final String name) {
			if(FORMSAUTH_SCRIPTABLE_OBJECT.equals(name)) { return authDisp; }
			return null;
		}
		@Override public Object get(final String name) {
			if(FORMSAUTH_SCRIPTABLE_OBJECT.equals(name)) { return authTemplateObj; }
			return null;
		}
	}

	private static final int OK = HttpStatus.OK.codeNumber();
	private static final int CREATED = HttpStatus.CREATED.codeNumber();

	/** This class implements the responses to the form POST events. It primarily delegates to an instance of
		{@link net.metanotion.formsauth.AuthAPI} and uses the status codes of it's results to return HTTP
		redirects to the appropriate web page to explain the results of the account/authentication operation. */
	private final class ServerFormsAuth implements FormsAuthAPI {
		@Override public HttpValues login(final StateMachine sm,
				final String username,
				final SecureString password,
				final String currentPage) {
			final HttpValues hv = authAPI.login(sm, username, password, currentPage);
			logger.debug("Here************************ {}", hv);
			if(hv.getHttpStatus() == OK) {
				return HttpUtil.newRedirect(currentPage);
			} else {
				return HttpUtil.newRedirect(prefix + LOGIN_FAILED_PAGE);
			}
		}

		@Override public HttpValues logout(final StateMachine sm) {
			authAPI.logout(sm);
			return HttpUtil.newRedirect(prefix + LOGGED_OUT_PAGE);
		}

		@Override public Object loadPage(final ObjectServer os, final RequestObject ro) {
			return templates.get("/pages/" + ro.getResource()).skin(wrapOS(os), wrapRO(ro));
		}

		@Override public HttpValues requestPasswordReset(final String username) {
			authAPI.requestPasswordReset(username);
			return HttpUtil.newRedirect(prefix + PASSWORD_RESET_REQUESTED_PAGE);
		}

		@Override public HttpValues resetPassword(final long rid,
				final String token,
				final SecureString password,
				final SecureString password2) {
			final HttpValues hv = authAPI.resetPassword(rid, token, password, password2);
			if(hv.getHttpStatus() == OK) {
				return HttpUtil.newRedirect(prefix + LOGIN_PAGE);
			} else {
				return HttpUtil.newRedirect(prefix + PASSWORD_RESET_PAGE);
			}
		}

		@Override public HttpValues createAccount(final StateMachine sm,
				final String username,
				final SecureString password,
				final SecureString password2,
				final RequestObject ro) {
			final HttpValues hv = authAPI.createAccount(sm, username, password, password2, ro);
			if(hv.getHttpStatus() == CREATED) {
				return HttpUtil.newRedirect(prefix + ACCOUNT_CREATED_PAGE);
			} else {
				return HttpUtil.newRedirect(prefix + CREATE_ACCOUNT_FAILED_PAGE);
			}
		}

		@Override public HttpValues addAccount(final UserToken ut,
				final String username,
				final SecureString password,
				final RequestObject ro) {
			final HttpValues hv = authAPI.addAccount(ut, username, password, ro);
			if(hv.getHttpStatus() == CREATED) {
				return HttpUtil.newRedirect(prefix + EDIT_ACCOUNT_PAGE);
			} else {
				return HttpUtil.newRedirect(prefix + ADD_ACCOUNT_FAILED_PAGE);
			}
		}

		@Override public HttpValues removeAccount(final UserToken ut, final String username, final SecureString password) {
			final HttpValues hv = authAPI.removeAccount(ut, username, password);
			if(hv.getHttpStatus() == OK) {
				return HttpUtil.newRedirect(prefix + EDIT_ACCOUNT_PAGE);
			} else {
				return HttpUtil.newRedirect(prefix + REMOVE_ACCOUNT_FAILED_PAGE);
			}
		}

		@Override public HttpValues changePassword(final UserToken ut,
				final SecureString oldPassword,
				final SecureString newPassword,
				final SecureString newPassword2) {
			final HttpValues hv = authAPI.changePassword(ut, oldPassword, newPassword, newPassword2);
			if(hv.getHttpStatus() == OK) {
				return HttpUtil.newRedirect(prefix + EDIT_ACCOUNT_PAGE);
			} else {
				return HttpUtil.newRedirect(prefix + CHANGE_PASSWORD_FAILED_PAGE);
			}
		}

		@Override public HttpValues sendAccountValidation(final String username) {
			authAPI.sendAccountValidation(username);
			return HttpUtil.newRedirect(prefix + EMAIL_VALIDATION_SENT_PAGE);
		}

		@Override public HttpValues validateAccount(final long vid, final String token) {
			final HttpValues hv = authAPI.validateAccount(vid, token);
			if(hv.getHttpStatus() == OK) {
				return HttpUtil.newRedirect(prefix + EMAIL_VALIDATED_PAGE);
			} else {
				return HttpUtil.newRedirect(prefix + VALIDATION_FAILED_PAGE);
			}
		}

		@Override public HttpValues validateAccountPage(final long vid, final String token) {
			final HttpValues hv = authAPI.validateAccount(vid, token);
			if(hv.getHttpStatus() == OK) {
				return HttpUtil.newRedirect(prefix + EMAIL_VALIDATED_PAGE);
			} else {
				return HttpUtil.newRedirect(prefix + VALIDATION_FAILED_PAGE);
			}
		}

		@Override public Object whoAmI(final UserToken ut) { return authAPI.whoAmI(ut); }

		@Override public Object api() { return authAPI.api(); }
	}

	private static final class SessionWrapper implements Unknown {
		private final FormScriptingObject fso;
		private final Unknown session;
		public SessionWrapper(final FormScriptingObject fso, final Unknown session) {
			this.fso = fso;
			this.session = session;
		}
		public static Dispatcher dispatcher() {
			logger.debug("Look up dispatcher: {}", fsoDisp);
			return fsoDisp;
		}
		@Override public <I> I lookupInterface(final Class<I> theInterface) {
			if(theInterface == Unknown.class) { return (I) this; }
			if(theInterface == Object.class) { return (I) this; }
			if(theInterface == FormScriptingObject.class) { return (I) fso; }
			return session.lookupInterface(theInterface);
		}
	}

	/** This method provides a scripting object so you can add authentication/account forms to your templates. An
		instance of this will need to be created for each session object in your application since it wraps the current
		session to provide access to the scripting objects in THAT session for the templates to use.
		@param session The instance of your session component to wrap so that services like
			{@link net.metanotion.scripting.ObjectServer} (especially if you're using custom auth templates).
		@return An instance variable that should be annotated as
			{@literal @}{@link net.metanotion.scripting.Scriptable}.
	*/
	public Unknown newSession(final Unknown session) { return new SessionWrapper(fso, session); }

	/** This method provides an instance of the authentication UI that the {@link #dispatcher} evaluates it's messages
		against. This method does not depend on the authentication state, and so only one needs to be provided per
		application if you want to put it in a globally available {@literal @}{@link net.metanotion.util.Service}
		annotation.
		@return An instance of the {@link net.metanotion.formsauth.FormsAuthAPI} interface that provides responses to
			user interface forms and pages to manage your user account.
	*/
	public FormsAuthAPI instance() { return ui; }

	private ObjectServer wrapOS(final ObjectServer os) {
		return new ObjectServerTree(authOS, os);
	}

	private static RequestObject wrapRO(final RequestObject ro) {
		final HashMap<String,Object> vars = new HashMap<>();
		final String cp = ro.getRawResource();
		vars.put(CURRENT_PAGE_VAR, cp);
		return new DelegatingRequestObject(ro, cp, vars);
	}

	/** This class contains the scripting methods available inside the /forms/*.html templates via the "FormsAuth."
		object. */
	private final class AuthForms implements AuthUI {
		@Override public String currentPage(final String currentPage) { return currentPage; }
		@Override public String emailID(final String emailID) { return emailID; }
		@Override public String resetID(final String resetID) { return resetID; }
		@Override public String token(final String token) { return token; }

		@Override public String loginAction() { return prefix + "login"; }
		@Override public String logoutAction() { return prefix + "logout"; }
		@Override public String requestPasswordResetAction() { return prefix + "requestPasswordReset"; }
		@Override public String resetPasswordAction() { return prefix + "resetPassword"; }
		@Override public String createAccountAction() { return prefix + "createAccount"; }
		@Override public String addAccountAction() { return prefix + "addAccount"; }
		@Override public String removeAccountAction() { return prefix + "removeAccount"; }
		@Override public String changePasswordAction() { return prefix + "changePassword"; }
		@Override public String sendAccountValidationAction() { return prefix + "sendAccountValidation"; }

		@Override public String createAccountURI() { return prefix + CREATE_ACCOUNT_PAGE; }
		@Override public String editAccountURI() { return prefix + EDIT_ACCOUNT_PAGE; }
		@Override public String requestPasswordResetURI() { return prefix + REQUEST_PASSWORD_RESET_PAGE; }

		@Override public Object loginForm(final String currentPage) {
			return ((Wrap) fso.loginForm(null, new BasicRequestObject(currentPage))).unwrap();
		}
		@Override public Object logoutForm(final String currentPage) {
			return ((Wrap) fso.logoutForm(null, new BasicRequestObject(currentPage))).unwrap();
		}
		@Override public Object removeAccountForm(final String emailID) {
			final HashMap<String,Object> vars = new HashMap<>();
			vars.put(EMAIL_ID_VAR, emailID);
			return ((Wrap) fso.removeAccountForm(authOS, new BasicRequestObject(HttpMethod.GET, SLASH, vars))).unwrap();
		}
		@Override public Object requestEmailValidationForm(final String emailID) {
			final HashMap<String,Object> vars = new HashMap<>();
			vars.put(EMAIL_ID_VAR, emailID);
			return ((Wrap) fso.requestEmailValidationForm(authOS, new BasicRequestObject(HttpMethod.GET, SLASH, vars))).unwrap();
		}
	}

	/** This class contains the methods available for scripting authentication UI chrome into a web page. To retrieve
		an instance of this class, please see {@link net.metanotion.formsauth.FormsAuth#newSession}. */
	private final class FormScriptingObject {
		public Object loginForm(@Named(OS_VAR) @Nullable @Service ObjectServer os, @Nullable @Named(RO_VAR) RequestObject ro) {
			return templates.get(LOGIN_FORM).skin(wrapOS(os), wrapRO(ro));
		}

		public Object logoutForm(@Named(OS_VAR) @Nullable @Service ObjectServer os, @Nullable @Named(RO_VAR) RequestObject ro) {
			return templates.get(LOGOUT_FORM).skin(wrapOS(os), wrapRO(ro));
		}

		/*	An account panel is authentication state aware, and also synthesized from multiple templates.
			When the user is authenticated, the panel consists of:
				Logout button.
				Edit Account Settings button(which pops up a dialog)
			When logged out, the panel, consists of:
				Login form.
				Request Password Reset button(which pops up a dialog)
				Create Account button(which pops up a dialog)
		*/
		public Object accountPanel(@Nullable @Named(OS_VAR) @Service final ObjectServer os,
				@Nullable @Named(UT_VAR) @Service final UserToken ut,
				@Nullable @Named(RO_VAR) final RequestObject ro) {
			final ObjectServer newOS = wrapOS(os);
			final RequestObject newRO = wrapRO(ro);
			if(ut != null) {
				return this.accountPanelLoggedInForm(newOS, newRO);
			} else {
				return this.accountPanelAnonymousForm(newOS, newRO);
			}
		}

		public Object accountPanelAnonymousForm(@Nullable @Named(OS_VAR) @Service final ObjectServer os,
				@Nullable @Named(RO_VAR) final RequestObject ro) {
			return templates.get(ACCOUNT_PANEL_ANONYMOUS_FORM).skin(wrapOS(os), wrapRO(ro));
		}

		public Object accountPanelLoggedInForm(@Nullable @Named(OS_VAR) @Service final ObjectServer os,
				@Nullable @Named(RO_VAR) final  RequestObject ro) {
			return templates.get(ACCOUNT_PANEL_LOGGED_IN_FORM).skin(wrapOS(os), wrapRO(ro));
		}

		public Object requestPasswordResetForm(@Nullable @Named(OS_VAR) @Service final ObjectServer os,
				@Nullable @Named(RO_VAR) final RequestObject ro) {
			return templates.get(REQUEST_PASSWORD_RESET_FORM).skin(wrapOS(os), wrapRO(ro));
		}

		public Object createAccountForm(@Nullable @Named(OS_VAR) @Service final ObjectServer os,
				@Nullable @Named(RO_VAR) final RequestObject ro) {
			return templates.get(CREATE_ACCOUNT_FORM).skin(wrapOS(os), wrapRO(ro));
		}

		public Object removeAccountForm(@Nullable @Named(OS_VAR) @Service final ObjectServer os,
				@Nullable @Named(RO_VAR) final RequestObject ro) {
			return templates.get(REMOVE_ACCOUNT_FORM).skin(wrapOS(os), wrapRO(ro));
		}

		public Object addAccountForm(@Nullable @Named(OS_VAR) @Service final ObjectServer os,
				@Nullable @Named(RO_VAR) final RequestObject ro) {
			return templates.get(ADD_ACCOUNT_FORM).skin(wrapOS(os), wrapRO(ro));
		}

		public Object changePasswordForm(@Nullable @Named(OS_VAR) @Service final ObjectServer os,
				@Nullable @Named(RO_VAR) final RequestObject ro) {
			return templates.get(CHANGE_PASSWORD_FORM).skin(wrapOS(os), wrapRO(ro));
		}

		public Object requestEmailValidationForm(@Nullable @Named(OS_VAR) @Service final ObjectServer os,
				@Nullable @Named(RO_VAR) final RequestObject ro) {
			return templates.get(REQUEST_EMAIL_VALIDATION_FORM).skin(wrapOS(os), wrapRO(ro));
		}

		public Object accountList(@Nullable @Named(OS_VAR) @Service final ObjectServer os,
				@Nullable @Named(UT_VAR) @Service final UserToken ut,
				@Nullable @Named(RO_VAR) final RequestObject ro) {
			final Object result = authAPI.whoAmI(ut);
			if(!(result instanceof AccountInfo)) { return ""; }
			final AccountInfo acctInfo = (AccountInfo) result;
			final HashMap<String,Object> vars = new HashMap<>();
			final RequestObject newRO = new DelegatingRequestObject(ro, vars);
			final ObjectServer newOS = wrapOS(os);
			final ArrayList list = new ArrayList();
			for(final Account acct: acctInfo.accounts) {
				vars.put(EMAIL_ID_VAR, acct.username);
				list.add(((Wrap) templates.get(ACCOUNT_LIST_ITEM_FORM).skin(newOS, newRO)).unwrap());
			}
			return list;
		}
		public String editAccountURI() { return authTemplateObj.editAccountURI(); }
	}
}