Gramex - DBAuth Login only when user is active - authentication

I am trying to implement a way by which I can check the value of an is_active column from user database before letting the user log in. So along with the password check, I also want to check if is_active column is True, only then the user must be able to login, otherwise I want restrict login and show an error that "The user is inactive"(even if the password is correct).
I went through the Gramex documentation but could not find a way to configure this requirement.
Is there any way to implement this in DBAuth?

If you want to validate if the is_active column in the database is true, and to prevent login before the fact, use prepare:. For example:
url:
login:
pattern: /login/
handler: DBAuth
kwargs:
prepare: mymodule.check_active(args)
...
... where mymodule.py has:
def check_active(args)
if handler.request.method == 'POST':
is_active = gramex.data.filter(
url, table='user', args={'user': args['user'], 'is_active': [True]})
if not is_active:
raise HTTPError(403, 'Cannot log in as inactive user')
More flexible solution. Create a subclass of DBAuth called ValidatedDBAuth:
import gramex.data
from tornado.gen import coroutine
from gramexenterprise.handlers import DBAuth
from gramex.config import variables
class ValidatedDBAuth(DBAuth):
#coroutine
def post(handler):
user = handler.get_argument('user')
is_active = gramex.data.filter(
variables['CONNECTION_STRING'],
table='user',
args={'user': [user]}
)['is_active'].iloc[0]
if is_active:
yield super().post()
else:
handler.redirect('/login/?msg=inactive')
Now use this instead of your DBAuth. For example:
url:
login:
pattern: /login/
handler: mymodule.ValidatedDBAuth
kwargs:
... # Same as for DBAuth

You can copy the default auth.template.html locally as custom-login.html.
Then you can check if the user is already logged in (i.e. is_active) by checking handler.current_user. For example:
{% set is_active = handler.current_user %}
{% if not is_active %}
<p>The user is inactive. You can't log in.</p>
{% else %}
... continue with default form
{% end %}
In your gramex.yaml, you could add template: $YAMLPATH/custom-login.html like this:
login:
pattern: /login/
handler: DBAuth
kwargs:
url: $YAMLPATH/auth.xlsx
user:
column: user
password:
column: password
template: $YAMLPATH/custom-login.html
NOTE: By only allowing logged-in users to log in, no user will be able to log in for the first time. So you'll need some mechanism to figure out how to let users log in for the first time.

Related

From attribute inside node mailer message not working always keep sending mail through default account which is used for creating account

let message={
from:email,
to:emails,
text:'Please join the meeting',
subject:`You are Invited for ${meetingName}`,
html:mail
}
Above is my message object the from attribute is not working. I want to send email from logged in user to the user given by him , but node mailer uses the email (my email which is configured at auth) it must use email given in from tag.
const config={
service:'gmail',
auth: {
user:"xx#gmail.com",
pass: ""
},
port:465,
host:'smtp.gmail.com'
}
always uses xx#gmail.com instead of logged in user

How to block url when the user has not verified their account ? (Symfony 6)

For my project (Symfony 6) I want to restrict some url as long as the user has not validated his account by clicking on the link received by email.
Because since i have make Registration (with symfony's website) I don't understand the interest of this point (account isVerified)
How to do this?
i've tried many things like modify security.yaml
You can give the user an additional role when they verify, then deny access if they don't have it in your controller like so:
// src/Controller/AdminController.php
// ...
public function adminDashboard(): Response
{
$this->denyAccessUnlessGranted('ROLE_VERIFIED');
// or add an optional message - seen by developers
$this->denyAccessUnlessGranted('ROLE_VERIFIED', null, 'User tried to access a page without having ROLE_VERIFIED');
}
https://symfony.com/doc/current/security.html#securing-controllers-and-other-code
You can also deny an entire pattern in security.yml:
# config/packages/security.yaml
security:
# ...
access_control:
# matches /users/verfied/*
- { path: '^/users/verfied', roles: ROLE_VERIFIED}
# matches /users/* except for anything matching the above rule
- { path: '^/users', roles: ROLE_USER }
https://symfony.com/doc/current/security.html#securing-url-patterns-access-control

Cognito unable to signup users that have unconfirmed status already

A Cognito User Pool is configured for the users to use their "email address" to sign up and sign in.
If a user signs up with the email of someone else then that email will get stuck in UNCONFIRMED state and the owner will not be able to use it appropriately.
Having said that let me provide an example with the following scenario:
User signs in with an email address the user doesn't own, let's say it is someone#mail.com. In this step (registration form) some more data is sent like organization name, and user full name.
Verification code is sent to the email
Now the user that owns someone#email.com wants to create an account (maybe some days in the future), so he goes and fills the registration form but an error is thrown by cognito {"__type":"UsernameExistsException","message":"An account with the given email already exists."}
Thinks to consider:
* If the email already exists but is in unconfirmed state then provide the user the option to resend the link. This option is not optimal because additional data might be already in the user profile as the 1st step exemplifies.
* A custom lambda can be done to delete the unconfirmed user before signup or as a maintenance process every day, but I am not sure if this is the best approach.
There is also this configuration under Policies in cognito consol: "How quickly should user accounts created by administrators expire if not used?", but as he name implies this setting will only apply to users if they are invited by admins.
Is there a proper solution for this predicament?
Amazon Cognito has provided pre-signup triggers for these functionality and auto signup also.Your thought is the same way as i have implemented that according to the cognito documentations.
Here I am using the amplify/cli which is the toolchain for my development purpose hence the lambda function used in the trigger is as below:
`
"use strict";
console.log("Loading function");
var AWS = require("aws-sdk"),
uuid = require("uuid");
var cognitoIdentityServiceProvider = new AWS.CognitoIdentityServiceProvider();
exports.handler = (event, context, callback) => {
const modifiedEvent = event;
// check that we're acting on the right trigger
if (event.triggerSource === "PreSignUp_SignUp") {
var params = {
UserPoolId: event.userPoolId,
Username: event.userName
};
cognitoIdentityServiceProvider.adminGetUser(params, function(err, data) {
if (err) {
console.log(err, err.stack);
} // an error occurred
else {
console.log("cognito service", data);
if (data.UserStatus == "UNCONFIRMED") {
cognitoIdentityServiceProvider.adminDeleteUser(params, function(
err,
data
) {
if (err) console.log(err, err.stack);
// an error occurred
else console.log("Unconfirmed user delete successful ");
// successful response
});
}
// successful response
}
});
return;
}
// Throw an error if invoked from the wrong trigger
callback('Misconfigured Cognito Trigger '+ event.triggerSource);
};
`
this will actually check and delete if the status is UNCONFIRMED using the aws-sdk methods adminGetUser and adminDeleteUser
hope this will help ;)
I got around this by setting ForceAliasCreation=True. This would allow the real email owner to confirm their account. The draw back is that you end up with 2 users. One CONFIRMED user and another UNCONFIRMED user.
To clean this up, I have a lambda function that calls list-users with filter for unconfirmed user and delete the accounts which were created before a certain period. This function is triggered daily by CloudWatch.
change to confirm from unconfirm:
aws cognito-idp admin-confirm-sign-up \
--user-pool-id %aws_user_pools_web_client_id% \
--username %email_address%

Symfony 4 login form with security and database users

I was a total noob on Symfony about a week ago and I thought I should just dive in Symfony 4. After a week of trying to solve the basic login problem, I believe the documentation is still missing some parts.
Now I've found a solution and I will share it along with some tips on what you might be doing wrong. First part of the answer is a list of suggestions, while the second part is the creation of a project with working login from scratch (supposing you already have composer installed and using a server like apache).
Part 1: Suggestions
403 Forbidden
Check the access_control: key in security.yaml. The order of the rules has impact, since no more than one rule will match each time. Keep most specific rules on top.
login_check
Make sure the form action sends you to the login_check path, or whatever you changed it to in security.yaml.
Also check that you have declared a route for the login_check path either in a controller or in routes.yaml.
input name
Symfony forms tend to encapsulate input names in an array, while it only expects them to be named _username and _password (you can change that in security.yaml) to count it as a login attempt. So inspect the inputs to make sure the name attributes are correct.
Part 2: Full Symfony 4 Login
Project Setup
Let's start by creating the project. Open cmd/terminal and go to the folder you want to contain the project folder.
cd .../MyProjects
composer create-project symfony/website-skeleton my-project
cd my-project
Now you have created a Symfony 4 website template in .../MyProjects/my-project and the cmd/terminal is in that path and will execute the rest of the commands properly.
Check in your .../MyProjects/my-project/public folder for a .htaccess file. If it exists you are fine, else run the following command.
composer require symfony/apache-pack
You can now find your site by visiting my-project.dev/public. If you want to remove this public path, you should do so using the .htaccess file, not moving the index.php.
Project Settings
1) Edit the DATABASE_URL key inside the .env file to correspond to your database settings.
2) Edit the config/packages/security.yaml file, so it looks like this:
security:
encoders:
App\Entity\User:
algorithm: bcrypt
providers:
user:
entity:
class: App\Entity\User
property: username
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: true
provider: user
form_login:
#login_path: login
#check_path: login_check
default_target_path: homepage
#username_parameter: _username
#password_parameter: _password
logout:
#path: /logout
#target: /
access_control:
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/, roles: ROLE_USER }
- { path: ^/admin, roles: ROLE_ADMIN }
Some explanation:
App\Entity\User is the User entity you 'll create in a while to handle the login.
The user provider is just a name that needs to have a match in providers and firewalls.
The logout key must be declared if you want to allow the user to... well, logout.
Values in #comment reveal the default value we'll be using later on and act as a reference of what you are more likely to change.
User Entity
A user must have a role, but could have more. So let's build a UserRole Entity first for a ManyToMany relationship.
php bin/console make:entity userRole
All entities start with an id property. Add a role too.
php bin/console make:entity user
User needs the username, password and roles properties, but you can add more.
Let's edit the src/Entity/User.php file:
Add the UserInterface interface to your User class.
use Symfony\Component\Security\Core\User\UserInterface;
class User implements UserInterface
Edit the generated getRoles(), to make it return string array.
public function getRoles(): array
{
$roles = $this->roles->toArray();
foreach($roles as $k => $v) {
$roles[$k] = $v->getRole();
}
return $roles;
}
getSalt() and eraseCredentials() are functions to implement the UserInterface interface.
public function getSalt()
{
return null;
}
public function eraseCredentials()
{
}
Using the bcrypt algorithm (as we set in security.yaml) we don't need a salt. It generates automatically one. No, you don't store this salt anywhere and yes, it will produce different hash for the same password every time. But yes, it will work somehow (magic...).
If you need a different algorithm, that uses salt, you need to add a salt property on the User entity.
Homepage
For testing purposes we will create a homepage
php bin/console make:controller homepage
Edit the generated src/Controller/HomepageController.php file to change the root to /
#Route("/", name="homepage")
Login Controller
php bin/console make:controller login
Edit the generated src/Controller/LoginController.php file to make it like this:
<?php
namespace App\Controller;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
use App\Form\LoginType;
class LoginController extends Controller
{
/**
* #Route("/login", name="login")
*/
public function index(AuthenticationUtils $authenticationUtils)
{
$error = $authenticationUtils->getLastAuthenticationError();
$lastUsername = $authenticationUtils->getLastUsername();
$form = $this->createForm(LoginType::class);
return $this->render('login/index.html.twig', [
'last_username' => $lastUsername,
'error' => $error,
'form' => $form->createView(),
]);
}
/**
* #Route("/logout", name="logout")
*/
public function logout() {}
/**
* #Route("/login_check", name="login_check")
*/
public function login_check() {}
}
Login Form
php bin/console make:form login
You don't have to associate it to the User entity.
Edit the generated src/Form/LoginType.php file to add this:
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
replace this:
$builder
->add('_username')
->add('_password', PasswordType::class)
->add('login', SubmitType::class, ['label' => 'Login'])
;
and add this function, to prevent Symfony from changing the input names you requested above by enclosing them in login[...]
public function getBlockPrefix() {}
Login Template
Edit the templates/login/index.html.twig file to add this code in the {% block body %} ... {% endblock %}:
{% if error %}
<div>{{ error.messageKey|trans(error.messageData, 'security') }}</div>
{% endif %}
{{ form_start(form, {'action': path('login_check'), 'method': 'POST'}) }}
{{ form_widget(form) }}
{{ form_end(form) }}
Database Generation
php bin/console doctrine:migrations:generate
php bin/console doctrine:migrations:migrate
This should have generated your database, according to your User and UserRole entities.
Generate Password
The following command will provide you with a hashed password you can directly insert into the database. The password will be hashed with the algorithm specified in security.yaml.
php bin/console security:encode-password my-password
Hope this helps!
Thank you very much, the documentation lacks some important things, but this works very good. For others, check the order of the access-control entries, because one misplaced entry may block the whole process.
access_control:
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/admin, roles: ROLE_ADMIN }
This was working, but this not
access_control:
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/admin, roles: ROLE_ADMIN }
- { path: ^/, roles: IS_AUTHENTICATED_ANONYMOUSLY }

Symfony2 - Redirection after successful login

I've just started working through the Symfony 2 tutorials. I have created a bundle with a user class and have tried to follow the instructions to set up a login process. I think I am nearly there, however I'm currently falling at the last hurdle.
I have set up a bundle: Dan\AuthBundle, which contains my user class and another bundle: Dan\HelloBundle which I want to allow only logged in users to access.
My security.yml file is as follows:
security:
encoders:
Dan\AuthBundle\Entity\User: sha512
providers:
main:
entity: { class: Dan\AuthBundle\Entity\User, property: username }
administrators:
entity: { class: DanAuthBundle:User }
firewalls:
secured_area:
pattern: ^/*
form_login:
check_path: /login_check
login_path: /login
always_use_default_target_path: false
default_target_path: /hello
access_control:
- { path: ^/hello/.* }
The main routing.yml file looks like this:
DanAuthBundle:
resource: "#DanAuthBundle/Resources/config/routing.yml"
prefix: /auth/
DanHelloBundle_homepage:
pattern: /hello/
defaults: { _controller: DanHelloBundle:Default:index }
login:
pattern: /login
defaults: {_controller: DanAuthBundle:Default:login }
login_check:
pattern: /login_check
I have created several instances of my user class manually.
If I try to access the url /hello, I correctly get redirected to the login page. If I enter incorrect details, I get the correct message(s) delivered in the template, however, when I log in with the correct details, I receive a 324 (empty response) error (at this time, the url displayed in the browser is login_check).
From reading the documentation, I thought I should be redirected to the page I was originally trying to access?
http://symfony.com/doc/current/book/security.html#using-a-traditional-login-form
By default, if the submitted credentials are correct, the user will be
redirected to the original page that was requested (e.g. /admin/foo).
If the user originally went straight to the login page, he'll be
redirected to the homepage. This can be highly customized, allowing
you to, for example, redirect the user to a specific URL.
Also, if I try to access the page after entering the correct details, I once again get redirected to the login page.
Can anyone see if I've missed anything obvious?
This is from my log file:
[2012-06-18 18:33:47] doctrine.DEBUG: SELECT t0.id AS id1, t0.username
AS username2, t0.salt AS salt3, t0.hashed_password AS hashed_password4
FROM User t0 WHERE t0.username = ? (["hello"]) [] [] [2012-06-18
18:33:47] security.INFO: User "hello" has been authenticated
successfully [] [] [2012-06-18 18:33:47] event.DEBUG: Listener
"Symfony\Component\Security\Http\Firewall::onKernelRequest" stopped
propagation of the event "kernel.request". [] [] [2012-06-18 18:33:47]
event.DEBUG: Listener
"Symfony\Bundle\FrameworkBundle\EventListener\RouterListener" was not
called for event "kernel.request". [] [] [2012-06-18 18:33:47]
event.DEBUG: Listener
"Symfony\Bundle\AsseticBundle\EventListener\RequestListener" was not
called for event "kernel.request". [] [] [2012-06-18 18:33:47]
event.DEBUG: Notified event "kernel.response" to listener
"Symfony\Component\Security\Http\Firewall\ContextListener::onKernelResponse".
[] [] [2012-06-18 18:33:47] security.DEBUG: Write SecurityContext in
the session [] []
Any advice appreciated.
Thanks.
// if you're using Symfony 2.0
$key = '_security.target_path';
// if you're using Symfony 2.1 or greater
// where "main" is the name of your firewall in security.yml
$key = '_security.main.target_path';
// try to redirect to the last page, or fallback to the homepage
if ($this->container->get('session')->has($key)) {
$url = $this->container->get('session')->get($key);
$this->container->get('session')->remove($key);
} else {
$url = $this->container->get('router')->generate('homepage');
}
return new RedirectResponse($url);
You need 2 listeners.
One to set in session last page
Second to redirect after succesfull login
That link will solve your problem: http://www.reecefowell.com/2011/10/26/redirecting-on-loginlogout-in-symfony2-using-loginhandlers/
just use getUser check in your respective action (where you are rendering the login form view) as below:
if($this->getUser()){
return $this->redirect($this->generateUrl('your-redirect-path-alise'));
}