{"id":15971485,"url":"https://github.com/seandenigris/tf-login","last_synced_at":"2025-04-04T15:44:49.620Z","repository":{"id":136811400,"uuid":"136996241","full_name":"seandenigris/TF-Login","owner":"seandenigris","description":"Port of http://www.smalltalkhub.com/#!/~sergio101/TF-Login/","archived":false,"fork":false,"pushed_at":"2019-12-06T15:06:34.000Z","size":222,"stargazers_count":2,"open_issues_count":1,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-02-10T01:41:25.564Z","etag":null,"topics":["authentication","pharo","seaside","smalltalk","smalltalkhub-port"],"latest_commit_sha":null,"homepage":"","language":"Smalltalk","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/seandenigris.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-06-12T00:35:33.000Z","updated_at":"2019-12-11T11:38:18.000Z","dependencies_parsed_at":null,"dependency_job_id":"1bec630b-e5ed-4b6c-94cc-7932645e9407","html_url":"https://github.com/seandenigris/TF-Login","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/seandenigris%2FTF-Login","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/seandenigris%2FTF-Login/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/seandenigris%2FTF-Login/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/seandenigris%2FTF-Login/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/seandenigris","download_url":"https://codeload.github.com/seandenigris/TF-Login/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247208064,"owners_count":20901568,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["authentication","pharo","seaside","smalltalk","smalltalkhub-port"],"created_at":"2024-10-07T20:21:44.984Z","updated_at":"2025-04-04T15:44:49.598Z","avatar_url":"https://github.com/seandenigris.png","language":"Smalltalk","funding_links":[],"categories":[],"sub_categories":[],"readme":"Originally from http://www.squeaksource.com/TFLogin.html, but tests were failing. It turns out that this was written for pharo 1.1.1. I wanted to use pharo 2, so I refactored the parts that didn't work in pharo 2. The following are the changes i made:\n\n- ReadStream was updated to use Fuel.\n- The tests now only support ZnClient and ZnServer.\n\nAll tests now pass, except for testCapacity test. I will look at this in the next week, but I thought it would be worthwhile to get it out.\n\nThis package provides basic user authentication, registration, and account management for Seaside.\n\nFeatures include:\n\n- User login component with username and password text fields, forgot username, forgot password, register, and login buttons.\n- User registration component with optional BowWave reCaptcha spambot protection.\n- Edit account settings component allows user to change username, password, email address and application-specific properties.\n- Optional email confirmation for registration and account changes. Email content is provided by the host application. Examples are included.\n- Multi-part HTML/plain text email.\n- Configurable confirmation email timeouts and cookie retention periods.\n- Forgot username and forgot password support.\n- New password validation by the host application (to enforce password rules like minimum-length, etc.)\n- Options to allow empty passwords, remember username in cookie, and automatic login on return to the website.\n- Host application can provide login filter block for such things as disabling accounts or limiting login frequency.\n- Host application can use TFLogin's email confirmation mechanism for application-specific purposes. Additional user-related objects may be included in the account edit form.\n- Persistence is provided by a storage adaptor that can be replaced with the persistence mechanism of your choice. The provided default file storage adaptor scales to 100,000 users. Each user's information is saved in a separate versioned file.\n- An application properties dictionary is provided that allows you to store and retrieve additional objects along with the user account information.\n- All settings are available in the Seaside configuration page. Test/demo application included.\n- TFLogin Guide document included (see below).\n- Tested with Seaside 3.0.3 and Pharo 1.1.1. Email does NOT work with prior versions of Seaside.\n\nAll feedback is welcome.\n\n# TFLogin Guide\n\nNote that the tests included in the TFLogin package require the following packages to run:\n\n    http://www.squeaksource.com/Soup\n        Soup\n\n    http://www.squeaksource.com/ZincHTTPComponent\n        Zinc-HTTP\n\n## Integrating TFLogin with Your Application\n\nTake a look at the TLTestApp to see an example of the steps below.\n\nThere are a number of steps to introducing TFLogin authentication to an existing Seaside application. They are described below:\n\n1. Create `MyCoolComponent class\u003e\u003e#initialize` like the following:\n```smalltalk\ninitialize\n\t\n\t| application |\n\tapplication := WAAdmin register: self asApplicationAt: self handlerName.\n\tapplication preferenceAt: #sessionClass put: TLSession.\n\n\t\"Add TL-specific configuration options\"\n\tapplication configuration\n\t\taddParent: TLConfiguration instance;\n\t\taddParent: WAEmailConfiguration instance\n```\n2. If you plan to use reCaptcha to protect your registration form from spammers, then you will need the BowWave reCaptcha package that you can obtain by evaluating:\n```smalltalk\n    Gofer it\n        url: 'http://www.squeaksource.com/BowWave';\n            package: 'BowWave-Captcha-Core';\n            package: 'BowWave-Captcha-Recaptcha';\n            load.\n```\nThen add lines like the following to `MyCoolComponent class\u003e\u003e#initialize`, substituting your public and private reCaptcha keys that you obtain free from http://www.google.com/recaptcha.\n```smalltalk\t\n    application configuration parents add: BWRecaptchaConfiguration instance.\n    application preferenceAt: #publicKey put: '{{your-public-key}}'.\n    application preferenceAt: #privateKey put: '{{your-private-key}}'.\n```\nNote that these preferences can also be set using your application's Seaside configuration page.\n\nIf you do not plan to use reCaptcha spambot protection, then it is not necessary to install the BowWave package.\n\n3. You can also set TFLogin preferences in `MyCoolComponent class\u003e\u003e#initialize` if you wish, or you can set the using the Seaside application config page.  Here is an example of what can be done in the initialize method:\n```smalltalk\n\tapplication preferenceAt: #sendRegistrationConfirmationEmail put: true.\n\tapplication preferenceAt: #confirmationTimeoutMinutes put: 10.\n\tapplication preferenceAt: #useRecaptchaInRegistrationForm put: false.\t\n\tapplication preferenceAt: #smtpServer put: 'localhost'.\t\n\tapplication preferenceAt: #confirmEmailChangeViaEmail put: false.\n\tapplication preferenceAt: #confirmAccountChangesViaEmail put: false.\n\tapplication preferenceAt: #allowEmptyPasswords put: false.\n\tapplication preferenceAt: #allowUsernameChange put: false.\n\tapplication preferenceAt: #allowRememberUsername put: true.\n\tapplication preferenceAt: #allowAutomaticLogin put: false.\n```\t\nNote that these preferences can also be set using your application's Seaside configuration page.\n\n4. In your component's instance-side `#initialize` method (not the class initialize method as described above) put the following to make the TFLogin components known to your application:\n```smalltalk\ninitialize\n\n\tsuper initialize.\n\tloginComponent := (TLLoginComponent appName: '\u003cyour-app-name\u003e').\n\tloginComponent onAnswer: [ :user | user ifNotNilDo: [ :u | self loggedIn: u ] ].\n\tself children add: loginComponent.\n\teditAccountComponent := (TLEditAccountComponent loginComponent: loginComponent).\n\teditAccountComponent onAnswer: [ self editingAccount: false ].\n\tself children add: editAccountComponent.\n```\nIf you don't already have one, you will need a children method that looks like this:\n```smalltalk\nchildren\n\t^ children ifNil: [ children := Bag new ]\n```\t\n\n5. If you will be using email confirmation, then you will need to tell TFLogin where your email sending methods are like this (also in the application initialize method). An example of the email sending methods referenced here is presented later on in this guide.\n```smalltalk\n\tloginComponent registrationConfirmationEmailSender: [ :url :email :timeout | \n\t\tself\n\t\t\tsendRegistrationConfirmationEmailTo: email\n\t\t\tconfirmUrl: url\n\t\t\ttimeoutMinutes: timeout  ].\n\tloginComponent passwordResetEmailSender: [ :url :email :timeout |\n\t\tself\n\t\t\tsendPasswordResetUrl: url\n\t\t\tto: email\n\t\t\ttimeout: timeout ].\n\tloginComponent usernameReminderEmailSender: [ :usernames :email | \n\t\tself\n\t\t\tsendUsernameReminderFor: usernames\n\t\t\tto: email ].\n\teditAccountComponent emailChangeConfirmationEmailSender: [ :url :email :timeout :newUser |\n\t\tself\n\t\t\tsendEmailChangeConfirmationTo: email\n\t\t\tconfirmUrl: url\n\t\t\ttimeout: timeout\n\t\t\tnewUser: newUser  ].\n\teditAccountComponent accountChangeConfirmationEmailSender: [ :url :email :timeout :newUser |\n\t\tself\n\t\t\tsendAccountChangeConfirmationTo: email\n\t\t\tconfirmUrl: url\n\t\t\ttimeout: timeout\n\t\t\tnewUser: newUser ].\n```\n\n6. If you want to support automatic login using username/password cookies for your users, you will need to include the following in your application's initialRequest method: \n```smalltalk\n\tloginComponent cookieLogin\n```\t\nThis will log the user in immediately before your application has had a change to display the loginComponent if they have the correct cookies defined (as would be the case if they checked the \"Log me in automatically when I return to this site\" checkbox when they last logged in.)\n\n7. Here is an example of a mail-sending method. The text would of course vary depending on your application and the specific message being sent.\n```smalltalk\nsendRegistrationConfirmationEmailTo: email confirmUrl: url timeoutMinutes: timo\n\t\"Compose and send email. Answer true on success, false on failure.\"\n\n\t| textBody htmlBody emailOk appName |\n\n\tappName := 'Login Test App'.\n\t\n\t\"Compose a pain text message.\"\n\ttextBody := (WriteStream on: String new)\n\t\t\u003c\u003c 'This email is in response to your request to register at '; \u003c\u003c appName; \u003c\u003c '.'; crlf;crlf;\n\t\t\u003c\u003c 'Direct your browser to the following URL within ';\u003c\u003c timo asString; \u003c\u003c ' minutes to confirm your registration.'; crlf;crlf;\n\t\t\u003c\u003c '         '; \u003c\u003c url; crlf;crlf;\n\t\t\u003c\u003c 'If you did not attempt to register for a'; \u003c\u003c appName; \u003c\u003c ' account then this message was sent in error and should be ignored.'.\t\n\n\t\"Compose a nice HTML message.\"\n\thtmlBody := WAHtmlCanvas builder fullDocument: true; render: [ :html |\n\t\thtml div\n\t\t\tstyle: 'font-size:11pt;';\n\t\t\twith: [\n\t\t\t\thtml div\n\t\t\t\t\tstyle: 'margin-bottom: 10px;';\n\t\t\t\t\twith: 'This email is in response to your request to register at ', appName, '.'.\n\t\t\t\thtml text: 'Click on the link below within ', timo asString, ' minutes to confirm your registration.'.\n\t\t\t\thtml div\n\t\t\t\t\tstyle: 'margin-left:20pt;margin-top:10px;margin-bottom:10px;';\n\t\t\t\t\twith: [\n\t\t\t\t\t\thtml anchor\n\t\t\t\t\t\t\turl: url;\n\t\t\t\t\t\t\twith: 'Confirm registration'].\n\t\t \t\thtml text: 'If the link above is unresponsive, copy and paste the URL below into your browser''s address field to confirm your registration.'.\n\t\t\t\thtml div\n\t\t\t\t\tstyle: 'margin-left:20pt;margin-top:10px;margin-bottom:10px;';\n\t\t\t\t\twith: url.\n\t\t\t\thtml text: 'If you did not attempt to register for a', appName, ' account then this message was sent in error and should be ignored.']].\n\n\t\"Send the message.\"\n\t(emailOk := self sendEmailTo: email subject:  appName, ' Registration - action required' text: textBody html: htmlBody)\n\t\tifFalse: [ Transcript cr; show: url ].\n\n\t^ emailOk\n\nsendEmailTo: toAddress subject: subj text: textBody html: htmlBody\n\t\"Send multi-part MIME email message.\"\n\t\n\t| sem em|\n\tem := TLMailMessage empty.\n\tem addAlternativePart: textBody contents contentType: 'text/plain'.\n\tem addAlternativePart: htmlBody contents contentType: 'text/html'.\n\tsem := em\n\t\tseasideMailMessageFrom: 'Registrar@' , self emailHost\n\t\tto: toAddress\n\t\tsubject: subj.\n\t[sem send] on: Exception do: [ :ex | ^ false ].\n\t^ true\t\n```\n\n8. In your application's `#renderContentOn:` you should render your loginComponent or editAccountComponent (from step 4 above) as embedded components. (They also support being \"called\" modally, but this functionality has not been tested.)\n\nIf the TLSession\u003e\u003e#user is nil, it means no-one is logged in and you might want to use this to determine whether to display the loginComponent. \n\nThe editAccountComponent is generally displayed in response to an application-provided button. In step 4 we assumed that this button would set the `editingAccount` variable to true and that this would cause the `editAccountComponent` to be rendered. You may wish to use another method to determine how to display the `editAccountComponent`.\n\nThe `loginComponent` will answer when a user has successfully logged in. This is handled in the block specified in step 4 above. In that block we call an application method called `#loggedIn`. You can do whatever you wish in that method.\n\nThe `editAccountComponent` will answer when new values have been entered and the user has supplied the correct password or has clicked the cancel button. In step 4 above the block specified for this sets the `editAccount` variable to false, which we are assuming will cause the `editAccountComponent` not to be rendered.\n\t\n\t\n## AJAX\n\nThe components with \"Ajax\" in their names, TLAjaxLoginComponent, TLAjaxForgotPasswordComponent, TLAjaxForgotUsernameComponent, and TLAjaxRegisterComponent interact with the user without perform full page refreshes. This makes them suitable for use in a JQuery dialog. See TLTestApp\u003e\u003e#renderLoginDialog: for an example of using TFLogin components in JQuery dialogs:\n```smalltalk\n\trenderLoginDialog: html\n\t\t(html div)\n\t\t\tclass: 'logindialog';\n\t\t\tscript: ((html jQuery new dialog)\n\t\t\t\ttitle: 'Pop-up login';\n\t\t\t\twidth: '540px';\n\t\t\t\tresizable: false;\n\t\t\t\tautoOpen: true;\n\t\t\t\tmodal: false);\n\t\t\twith: [\n\t\t\t\thtml render: ajaxLoginComponent  ]\n```\t\t\nNote that to use JQueryUI components you must load the JQuery libraries in your application class #initialize method like this:\n```smalltalk\n\tapplication \n \t       addLibrary: JQDeploymentLibrary;\n  \t      addLibrary: JQUiDeploymentLibrary.\n```\nand use TLAjaxLoginComponent in place of TLLoginComponent in your application #initialize method.\n\n\t\n## New Password Validation\n\nYou can constrain passwords selected by your users to obey whatever rules you choose to define. In your application  initialization method, send a one-argument block to TLLoginComponent\u003e\u003e#passwordValidator:. This block will be passed the candidate password. The block should evaluate to nil if the password is acceptable, or to a string containing the error message to be shown to the user if it is not. The validator block is evaluated during user registration, when the user changes their password in the EditAccountComponent, and when the user resets their password when it has been forgotten.\n\nHere is an example from TLTestApp:\n\nIn TLTestAPP\u003e\u003einitialize\n```smalltalk\nloginComponent passwordValidator: [ :pswd | self validatePassword: pswd ].\n```\n```smalltalk\nvalidatePassword: password\n    ^ password size \u003c self minimumPasswordLength\n            ifTrue: [ 'Password must be at least ', self minimumPasswordLength asString, ' characters long.']\n            ifFalse: [ nil ]\n```\n```smalltalk\nminimumPasswordLength\n    ^ 6\n```\n\n## Logging Out\n\nTLLoginComponent provides no logout button as it's placement is generally application-specific. Your logout button, if you have one, should send loginComponent\u003e\u003e#logout. Among other tasks, this will set the session user to nil. Thereafter, you may wish to unregister your session like this:\n```smalltalk\n        loginComponent logout.\n        self session unregister.\n```\n\n## User Administration\n\nTFLogin provides no components for administration of users at this time.\n\nYou can obtain a list of all usernames by evaluating\n```smalltalk\n    (TLAuthenticationManager name: 'your-app-name') allUsernames.\n```\nYou can prepare reports on users using the following code fragment as a beginning\n```smalltalk\n    | authmgr |\n    authmgr := (TLAuthenticationManager name: 'LoginTestApp').\n    authmgr allUserIds do: [ :each |\n        | user |\n        user := authmgr userForId: each.\n        Transcript cr; show: 'User ', user username, ' last login at ',\n            user lastLoginFormatted, ' from ',\n            user lastLoginFrom]\n```\n\n## Deleting User Accounts\n\nTo delete the current user account, use the TLLoginComponent\u003e\u003e#deleteLoggedInUser method. Here's an example:\n```smalltalk\n    loginComponent deleteLoggedInUser.\n```\nTo delete a specific user you can evaluate something like this:\n```smalltalk\n    (TLAuthenticationManager  name: 'your-app-name') deleteUserByUsername: 'the-username-to-delete'\n```\nwhere your-app-name is your application name as provided in TLLoginComponent\u003e\u003e#initialize.\n\n## Login Filter\n\nIf you provide a one-argument block to TLLoginComponent\u003e\u003e#onLogin: it will be evaluated after a logging-in user's username and password have been validated, but before they have gaind access. The argument passed to the block is the logging-in user object. In the block you can access all the user object methods, including applicationProperties. If the block evaluates to nil, the user is allowed access. To disallow access, the block should evaluate to a string to be displayed to the user explaining that they are being denied access.\n\nLogin filters can be used to disable accounts temporarily, limit the frequency of logins, logging, etc.\n\nHere is an example from LoginTestApp. There is a button labeled \"Disable my account for two minutes\" presented to logged-in users. Clicking the button sends disableMe:\n```smalltalk\ndisableMe\n    self session user applicationProperties at: 'disabled' put: (DateAndTime current) + 2 minutes.\n    self loggedOut\n```\t\nIn LoginTestApp initialize, the login filter is established like this:\n```smalltalk\n    loginComponent onLogin: [ :user | self loginFilter: user ].\n```\nHere is the login filter method itself:\n```smalltalk\nloginFilter: user\n    | until |\n    until := user applicationProperties at: 'disabled' ifAbsent: [ ^ nil ].\n    until \u003c DateAndTime current\n        ifTrue: [\n            user applicationProperties removeKey: 'disabled'.\n            ^ nil ]\n        ifFalse: [\n            ^ 'Your account is disabled. Try again later.']\n```\t\n\n## Managing Login Failures\n\nSimilar to the login filter, if you supply a two-argument block to TLLoginComponent\u003e\u003e#onLoginFail:, it will be evaluated when a user login attempt fails. The arguments passed to the block are the username and remote address of the failed login attempt. The block should evaluate to nil or text to be displayed to the user inplace of the standard authorization error message (TLLoginComponent\u003e\u003e#authenticationErrorText).\n\nThis can be used, together with application properties and a login filter to implement failed login policies such as disabling accounts after too many failed login attempts or ignoring logins from remote addresses that have been repeatedly attempting to login using many usernames.\n\nHere is the code from TLTestApp that implements a maximum failed login attempts policy:\n\nIn TLTestApp initialize:\n```smalltalk\n    loginComponent onLogin: [ :user | self loginFilter: user ].\n    loginComponent onLoginFail: [ :username :address | self loginFailedUser: username from: address ].\n```\n```smalltalk\nloginFailedUser: username from: address\n    \"After the configured number of consecutive failed login attempts, disable the\n    account for 2 minutes. Answer nil or text to be displayed in place of the normal\n    authorization failure message.\"\n\t\n    ^ (loginComponent authenticationManager usernameExists: username)\n        ifTrue: [\n            | failedUser failureCount |\n            failedUser := loginComponent authenticationManager userForUsername: username.\n            failureCount := failedUser applicationProperties at: 'failedLoginAttempts' ifAbsent: [ 0 ].\n            failureCount \u003c self maximumFailedLoginAttempts\n                ifTrue: [\n                    failedUser applicationProperties\n\t\t\t\tat: 'failedLoginAttempts'\n\t\t\t\tput: (failureCount  + 1).\n                    loginComponent saveUser: failedUser.\n                    nil ]\n                ifFalse: [\n                    failedUser applicationProperties\n\t\t\t\tat: 'disabledForLoginFailures'\n\t\t\t\tput: (DateAndTime current) + 2 minutes.\n                    loginComponent saveUser: failedUser.\n                    self disabledForLoginFailuresText ]]\n        ifFalse: [ nil ]\n```\n```smalltalk\nloginFilter: user\n    \"Return nil to allow login, or text to be displayed to the user if login is disallowed.\n    First we check for disable from login failures, then for user requested disable.\"\n\t\n    ^ (self loginFilterDisableForLoginFailures: user) ifNil: [self loginFilterDisabledByUser: user ]\n```\n```smalltalk\nloginFilterDisableForLoginFailures: user\n    \"If logins have been disabled for too many login failures, return text to present to user,\n    otherwise nil to allow login.\n\t\n    Note that we use the same message here as is used in #loginFailedUser:from: so that\n    there is no indication to the user that this time the correct password was actually entered.\" \n\n    | until |\n    ^ (user applicationProperties includesKey: 'disabledForLoginFailures')\n        ifTrue: [\n            until := user applicationProperties at: 'disabledForLoginFailures'.\n            until \u003c DateAndTime current\n                ifTrue: [\n                    user applicationProperties removeKey: 'disabledForLoginFailures'.\n                    nil ]\n                ifFalse: [\n                    self disabledForLoginFailuresText ]]\n        ifFalse: [ nil ]\n```\n```smalltalk\nloggedIn: user\n\t\"sent from onAnswer in self initialize.\"\n\t\n\tuser applicationProperties removeKey: 'failedLoginAttempts' ifAbsent: [].\n```\n\n## Auto-login\n\nYou may allow your user to specify that they be logged in automatically when they return to your site. This is accomplished by defining cookies that contain the user's base64 encoded username and an encrypted copy of their password.\n\nEven though the password cookie content is encrypted, use of this feature presents a serious security risk. Consider allowing it only over secure (TLS/SSL) sessions or only when revealing the user's content to unauthorized users is not a problem.\n\nNote that the TFLoginComponent\u003e\u003e#logout method clears the password cookie. Otherwise users with auto-login would be automatically logged in immediately after logout and would have no opportunity to change their auto-login setting. In general, users who have specified auto-login should not logout if they wish to be logged in automatically when they return.\n\n\nUsing TFLogin's email confirmation for your own purposes\n\nYou can cause TFLogin to send an email confirmation message and evaluate a block that you provide when the user navigates to the confirmation URL. This feature uses the same mechanism as is used for registration confirmation, username reminders, password resets, and account change confirmations.\n\nThe TLPendingUserAction is a subclass of TLUser and is a copy of the user for which the action is being confirmed. The confirm block that you provide is passed the TLPendingUser object. Note that when it is evaluated, the session context will be different than when the pending action was queued.\n\nHere is an example from TLTestApp. Other referenced methods can be seen in the TLTestApp source code.\n```smalltalk\nconfirmWithUser\n    | url userAction |\n        \"Instantiate a TLPendingUserAction object for your user and\n        provide a one-argument block to be evaluated when the user confirms.\"\n        userAction := TLPendingUserAction \n            forUser: self session user\n            onConfirmDo: [ :confirmedAction |\n                \"In our action we will log the user in and set a flag indicating that\n                they have confirmed.\"\n                loginComponent loginUserById: confirmedAction userId.\n                self testConfirmed: true ].\n\t\n        \"Queue the pending user action. It will remain active for the configured\n        confirmation timeout period. This method answers the confirmation URL\n        to which the user must navigate to confirm the action.\" \n        url := loginComponent addPendingUserAction: userAction.\n\t\n        \"Send a confirmation email to the user.\"\n        self\n            sendTestConfirmationUrl: url\n            to: self session user email\n            timeout: (self application preferenceAt: #confirmationTimeoutMinutes)\t\n```\n\nNew user initialization\n\nIf you provide a one-argument block to the TLLoginComponent\u003e\u003e#onRegistration: method, your block will be evaluated with pending new user objects before the registration email confirmation (if any) is sent. Your block should answer true to allow the registration to proceed, or false to cancel the registration without further interaction with the user. If you return false, you should arrange to inform the user as to why their registration attempt is being rejected.\n\nIn your onRegistration block, you can populate the new user's applicationProperties dictionary with initial values. This can be useful if, for example, you have allowed an unregistered user to work at your website and for them to save their work you require them to register for an account. Since the registration confirmation will take place in another session the question arises as to where to save their work during the registration confirmation process (and how to dispose of it if the registration is not confirmed.) Saving the user's work in the pending user object's applicationProperties dictionary provides the answer. When the new user logs in the first time, anything placed in their applicationProperties dictionary in the onRegister block will be present in their TLSession user object.\n\nHere is an example onRegistration block in which userObjects are saved in the pending user's applicationProperties:\n```smalltalk\n\tloginComponent: onRegistration: [ :pendingUser | \n\t\tpendingUser applicationProperties\n\t\t\tat: 'userObjects'\n\t\t\tput: self userObjects.\n\t\ttrue]\n```\n\n## Appearance\n\nThe components used by TFLogin are generally quite plain. CSS class names are present to allow you to use whatever CSS you need to allow them to blend in with your application.\n\nIf more customization is needed, you can substitute your own components for the default ones by setting the component class to be used. Where you see a defaultXxxComponent method where XxxComponent is the component you wish to replace, use method XxxComponent: to specity your replacement component class.\n\n## Persistence\n\nPersistence is accomplished by TLAuthenticationManager using an instance of TLStorageAdaptor. The default storage adaptor is TLCachingFileStorageAdaptor (see below). To use a different persistence mechanism, derive a new class from TLStorageAdaptor, implementing the subclassResponsibility methods. Then, in your application's initialization method, after instantiating TLLoginComponent set the storage adaptor like this:\n```smalltalk\n    myTLLoginComponent authenticationManager storageAdaptor: MyStorageAdaptor new. \n```\nNote that pending unconfirmed user registrations, account changes, and password resets are not persisted and thus if the Smalltalk image is terminated without saving, they will be lost.\n\n## TLFileStorageAdaptor \n\nThe default storage adaptor may, in some cases, serve all of your application's persistence needs. TLCachingFileStorageAdaptor uses  an instance of TLMultiFileDatabase to manage file-based persistence of TLRegisteredUser objects. Each user object is read and written individually to its own file.\n\nNumbered versions of each user file are maintained. By default, two versions are retained for each user. The files are named with the userId, the base36-encoded username and the base36-encoded email address. This allows TLCachingFileStorageAdaptor to use the file system's wildcarding support to select files by userId, username, or email address. Base-36 encoding is used to ensure that no characters disallowed by the file system appear in the filenames.\n\nThe default database directory created in the Pharo Contents/Resources directory is named app-nameUserDb where app-name is the value passed as the TLLoginComponent class\u003e\u003e#appName: during initialization.\n\nTLCachingFileStorageAdaptor caches (by default) the 100 most recently accessed user objects and all usernames and associated userIds. File writes are queued to a background writer process to avoid delaying response to the user. Files are spread across an automatically-created sub-directory tree to avoid the performance penalty that occurs when a very large number of files is present in a single directory. TLCachingFileStorageAdaptor has been tested with 100,000 users and performs well with that user population.\n\n## Application Properties\n\nRegisteredUser contains a dictionary accessed by the #applicationProperties method that is by default empty. The host application may store arbitrary per-user objects in the dictionary or instances of subclasses of TLApplicationPropertyItem.\n\nObjects placed in the applicationProperties of RegisteredUsers are saved and restored along with the rest of the user information. Applications can cause the current user to be saved, perhaps after having updated applicationProperties, with the TLLoginComponent \u003e\u003e#saveUser method.\n\nDepending on the values of its settings, TLApplicationPropertyItem objects may optionally be included in the information displayed and optionally edited by the user in the TLEditAccountComponent. Objects not derived from TLApplicationPropertyItem found in the application properties dictionary are ignored by the TLEditAccountComponent.\n\n\n## Re-initialization\n\nTo remove all pending actions such as password resets, new registrations, and account changes, evaluate \n```smalltalk\n\tTLAuthenticationManager  initialize.\n```\nTo delete the user database, evaluate \"(TLFileStorageAdaptor name: \u003capp-name\u003e) deleteDatabase\" where app-name is your application name as provided in TLLoginComponent\u003e\u003einitialize. Example:\n```smalltalk\n      (TLFileStorageAdaptor name: 'LoginTestApp') deleteDatabase\n```\nAlternatively, you could delete the directory named \u003capp-name\u003eUserDb in the Pharo Contents/Resources directory.\n\n\n\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fseandenigris%2Ftf-login","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fseandenigris%2Ftf-login","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fseandenigris%2Ftf-login/lists"}