Perform extra validation after a Student signs into Moodle - moodle

I am hoping someone can point me in the right direction. We host a University Moodle site and we are looking for a way in which we can perform extra validation on a Student whenever they login. I will give a scenario.
We have an endpoint with a list of email addresses of students allowed to use the system, for example a list of Students who are fully paid up on tuition. Therefore, we are looking for a way to hook into the login process, perform this check and the allow the student to continue or redirect back to the login page with an error.
I would appreciate any advice on how we can achieve this. Thank you.

I found a solution to my problem. I ended up creating a custom Authentication plugin using the guidelines from https://docs.moodle.org/dev/Authentication_plugins. With that knowledge, I used the copied the folder in the Moodle installation path auth/none and used that as a shell for my new plugin. I went ahead and customized the plugin names to what I needed. Once that was done and once the plugin was installed and enabled from the Administrator Dashboard, I had something like this in my auth.php file:
// Required for all auth plugins
public function user_login($username, $password)
{
return false;
}
// Hooks in immediately after the User submits the login form
public function loginpage_hook()
{
$username = $_REQUEST['username'] ?? '';
/** CODE CHECKING IF USERNAME IS ALLOWED TO ACCESS MOODLE **/
/** FOR EXAMPLE CHECK IF USER PAID FEES **/
$userHasPaidFees = api_checks_if_user_paid_fees($username);
if ($userHasPaidFees ) {
// Returning true here proceeds with the
// normal Username/Password login combination
return true;
}
// If not, redirect them back to Login
// Or any other page and notify
redirect(
new moodle_url('/login/index.php'),
'Message telling user why they were not able to sign in',
null,
\core\output\notification::NOTIFY_ERROR
);
}
Thanks and I hope someone finds this useful.

Related

Laravel - Different $URL after verification Email

In my app, there are two types of users. A group of them, as admins, must register themselves, and the other can only be present after being invited by the admins. Both types of users get verification email. I want the page that these two types of users see, after click on button in verification email, is different. How can I do this?
Thanks for any help
If You are using laravel/ui for authentication you need to provide path where the user is to be taken based on type in your VerificationController which ensure where to redirect your user after it is verified.
you need to remove protected $redirectTo = RouteServiceProvider::HOME; and replace with below function
public function redirectTo()
{
if(auth()->user()->type == 'admin'){
return '/here';
}else{
retuurn 'there'
}
}
if you are using anything other than laravel/ui you can follow the same procedure and have to redirect manually to certain url

Redirect after login in drupal 8

I'm trying to redirect users after login to the destination set in the url, like : /user/login?destination=my-modules
I'm using this module to redirect to login page instead of showing a 403 page : https://www.drupal.org/project/r4032login
It works well and generates the URL with the '?destination=' parameter but after i login i'm redirected to homepage everytime.
The module is supposed to manage this by itself, but i still tried to create a custom module to do the redirection, i created a custom module and installed it, but nothing happens still.
Here is my code :
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
/**
* Implements hook_form_FORM_ID_alter().
*/
function test_redirect_form_user_login_form_alter(&$form, FormStateInterface $form_state, $form_id) {
$form['#submit'][] = 'test_redirect_user_login_form_submit';
}
/**
* Custom submit handler for the login form.
*/
function test_redirect_user_login_form_submit($form, FormStateInterface $form_state) {
$url = Url::fromRoute('a route');
$form_state->setRedirectUrl($url);
}
How do i properly do this ? Thank you
I know you asked for help with your code. I'm not super good at custom modules.
Maybe this module will help you:
https://www.drupal.org/project/login_destination
I've used it and it's pretty good but may not fit your needs.
Once you install it, you will have the option to set different login destinations for different users found at [mysite.com]/admin/config/people/login-destination
User Redirect (Redirect user after Login or Logout) helps to redirect the user after login or logout activity. This module is compatible to the Drupal latest versions and has full security coverage.

Swift2 Firebase: Is the email check done on the backend server? [duplicate]

Question says it all. In Firebase, how do I confirm email when a user creates an account, or, for that matter, do password reset via email.
I could ask more broadly: is there any way to send emails out from Firebase? E.g. notifications, etc. This isn't the kind of thing you would usually do client-side.
Update
Note that this was never a very secure way of handling email verification, and since Firebase now supports email verification, it should probably be used instead.
Original answer
I solved the email verification using the password reset feature.
On account creation I give the user a temporary (randomly generated) password. I then trigger a password reset which will send an email to the user with a link. The link will allow the user to set a new password.
To generate a random password you can use code similar to this:
function () {
var possibleChars = ['abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!?_-'];
var password = '';
for(var i = 0; i < 16; i += 1) {
password += possibleChars[Math.floor(Math.random() * possibleChars.length)];
}
return password;
}
Note that this is happening on the client, so a malicious user could tamper with your logic.
This would need to be done outside of firebase. I store users at /users/ and keep a status on them (PENDING, ACTIVE, DELETED). I have a small service that monitors users of a PENDING status and sends out a confirmation email. Which has a link to a webservice I've created to update the user status to ACTIVE.
[Engineer at Firebase - Update 2014-01-27]
Firebase Simple Login now supports password resets for email / password authentication.
Each of the Simple Login client libraries has been given a new method for generating password reset emails for the specified email address - sendPasswordResetEmail() on the Web and Android, and sendPasswordResetForEmail() on iOS.
This e-mail will contain a temporary token that the user may use to log into their account and update their credentials. This token will expire after 24 hours or when the user changes their password, whichever occurs first.
Also note that Firebase Simple Login enables full configuration of the email template as well as the sending address (including whitelabel email from your domain for paid accounts).
To get access to this feature, you'll need to update your client library to a version of v1.2.0 or greater. To grab the latest version, check out https://www.firebase.com/docs/downloads.html.
Also, check out https://www.firebase.com/docs/security/simple-login-email-password.html for the latest Firebase Simple Login - Web Client docs.
As at 2016 July, you might not have to use the reset link etc. Just use the sendEmailVerification() and applyActionCode functions:
In short, below is basically how you'll approach this, in AngularJS:
// thecontroller.js
$scope.sendVerifyEmail = function() {
console.log('Email sent, whaaaaam!');
currentAuth.sendEmailVerification();
}
// where currentAuth came from something like this:
// routerconfig
....
templateUrl: 'bla.html',
resolve: {
currentAuth:['Auth', function(Auth) {
return Auth.$requireSignIn() // this throws an AUTH_REQUIRED broadcast
}]
}
...
// intercept the broadcast like so if you want:
....
$rootScope.$on("$stateChangeError", function(event, toState, toParams, fromState, fromParams, error) {
if (error === "AUTH_REQUIRED") {
$state.go('login', { toWhere: toState });
}
});
....
// So user receives the email. How do you process the `oobCode` that returns?
// You may do something like this:
// catch the url with its mode and oobCode
.state('emailVerify', {
url: '/verify-email?mode&oobCode',
templateUrl: 'auth/verify-email.html',
controller: 'emailVerifyController',
resolve: {
currentAuth:['Auth', function(Auth) {
return Auth.$requireSignIn()
}]
}
})
// Then digest like so where each term is what they sound like:
.controller('emailVerifyController', ['$scope', '$stateParams', 'currentAuth', 'DatabaseRef',
function($scope, $stateParams, currentAuth, DatabaseRef) {
console.log(currentAuth);
$scope.doVerify = function() {
firebase.auth()
.applyActionCode($stateParams.oobCode)
.then(function(data) {
// change emailVerified for logged in User
console.log('Verification happened');
})
.catch(function(error) {
$scope.error = error.message;
console.log(error.message, error.reason)
})
};
}
])
And ooh, with the above approach, I do not think there's any need keeping the verification of your user's email in your user data area. The applyActionCode changes the emailVerified to true from false.
Email verification is important when users sign in with the local account. However, for many social authentications, the incoming emailVerified will be true already.
Explained more in the article Email Verification with Firebase 3.0 SDK
What I did to work around this was use Zapier which has a built in API for firebase. It checks a location for added child elements. Then it takes the mail address and a verification url from the data of new nodes and sends them forwards. The url points back to my angular app, which sets the user email as verified.
As I host my app files in firebase, I don't need have to take care of any servers or processes doing polling in the background.
There is a delay, but as I don't block users before verifying mails it's ok. Zapier has a free tier and since I don't have much traffic it's a decent workaround for time being.
The new Firebase SDK v3 appears to support email address verification, see here (put your own project id in the link) but it doesn't appear to be documented yet.
I have asked the question on SO here
See #SamQuayle's answer there with this link to the official docs.
As noted by various others Firebase does now support account related emails but even better, as of 10 days ago or so it also supports sending any kind of email via Firebase Functions. Lots of details in the docs and example code here.
I used following code to check the email verification after creating new account.
let firAuth = FIRAuth.auth()
firAuth?.addAuthStateDidChangeListener { auth, user in
if let loggedUser = user {
if loggedUser.emailVerified == false {
loggedUser.sendEmailVerificationWithCompletion({ (error) in
print("error:\(error)")
})
}
else {
print(loggedUser.email)
}
} else {
// No user is signed in.
print("No user is signed in.")
}
}
I used MandrillApp. You can create an API key that only allows sending of a template. This way even thought your key is exposed it can't really be abused unless someone wants to fire off tonnes of welcome emails for you.
That was a hack to get myself off the ground. I'm now enabling CORS from a EC2 that uses the token to verify that the user exists before extending them a welcome via SES.

Google Sign-In with Passportjs not getting authenticated

I'm using Sails with Passport for authentication. I'm using passport-google-oauth(OAuth2Strategy) and passport-facebook for enabling Google Sign-in.
I'm not too well-versed with Passport, so pardon me if this is a rookie question. I've set up login via Facebook and it works just fine. With Google, I do receive an authorization code after allowing access to the app, but the I'm eventually not authenticated. I'm guessing the same code should work for both Facebook and Google since the strategies are both based on oauth2.
I'm not even sure what code to share, since I'm using the auto-generated code from sails-generate-auth, but do let me know if there's anything else I can share.
Any ideas on why this might be happening? The app is locally hosted but that's unlikely to be the problem since I am getting to the authorization stage anyway.
I faced the same problem and it was located here in in api/services/passport.js:
// If the profile object contains a list of emails, grab the first one and
// add it to the user.
if (profile.hasOwnProperty('emails')) {
user.email = profile.emails[0].value;
}
// If the profile object contains a username, add it to the user.
if (profile.hasOwnProperty('username')) {
user.username = profile.username;
}
// If neither an email or a username was available in the profile, we don't
// have a way of identifying the user in the future. Throw an error and let
// whoever's next in the line take care of it.
if (!user.username && !user.email) {
return next(new Error('Neither a username nor email was available'));
}
The Google service was not returning a profile.username property.
Because of it, the user is not saved in the database and cannot be authenticated. Then the passport callback receives an empty user, so the function that handles errors is fired and the user is redirected to the login page.
This change allows to use the displayName property as the username:
// If the profile object contains a list of emails, grab the first one and
// add it to the user.
if (profile.hasOwnProperty('emails')) {
user.email = profile.emails[0].value;
}
// If the profile object contains a username, add it to the user.
if (profile.hasOwnProperty('username')) {
user.username = profile.username;
}
/** Content not generated BEGIN */
// If the username property was empty and the profile object
// contains a property "displayName", add it to the user.
if (!user.username && profile.hasOwnProperty('displayName')) {
console.log(profile); // <= Use it to check the content given by Google about the user
user.username = profile.displayName;
}
/** Content not generated END */
// If neither an email or a username was available in the profile, we don't
// have a way of identifying the user in the future. Throw an error and let
// whoever's next in the line take care of it.
if (!user.username && !user.email) {
return next(new Error('Neither a username nor email was available'));
}
You could also use the profile.id property because profile.displayName is not necessarily unique (ie: two Google accounts can have an identical displayName). But it is also true accross different services: a Twitter account could also have the same username than a Facebook account. If both register on your application, you will have a bug. This is a problem from the code generated by sails-generate-auth and you should adapt it with the behavior that you want.
I will propose a PR if this solution works for you too.
Alright, so this ultimately turned out to be a known issue with the API.
TL;DR: Enable the Google+ API and the Contacts API as mentioned here. (The Contacts API isn't required, as #AlexisN-o pointed out in the comments. My setup worked as desired with Contacts API disabled. This obviously depends on what scope you're using.)
I believe it's not a nice way of failing since this was an API error that was prevented from bubbling up. Anyway, I dug into passport.authenticate to figure out what was going wrong. This eventually calls the authenticate method defined in the package corresponding to the strategy (oauth2 in this case). In here (passport-google-oauth/lib/passport-google-oauth/oauth2.js) I found that the accessToken was indeed being fetched from Google, so things should be working. This indicated that there was a problem with the requests being made to the token urls. So I ventured a little further into passport-oauth2/lib/strategy.js and finally managed to log this error:
{ [InternalOAuthError: failed to fetch user profile]
name: 'InternalOAuthError',
message: 'failed to fetch user profile',
oauthError:
{ statusCode: 403,
data: '{
"error": {
"errors": [{
"domain": "usageLimits",
"reason": "accessNotConfigured",
"message": "Access Not Configured. The API (Google+ API) is not enabled for your project. Please use the Google Developers Console to update your configuration.",
"extendedHelp": "https://console.developers.google.com"
}],
"code": 403,
"message": "Access Not Configured. The API (Google+ API) is not enabled for your project. Please use the Google Developers Console to update your configuration."
}
}'
} }
This was the end of the hunt for me and the first result for the error search led to the correct answer. Weird fix though.

Clear all Facebook profile fields and manually fill out the registration form?

You know how users can clear the pre-populated form and revert to normal registration?
I'm developing an iframe registration app and when the user clears the form fields, it looks like the signed_request is still valid (if upon load the user was logged into facebook).
Anyone know how we are supposed to know if the user is really using FB info or registration info? I previously thought the session would tell us but my session is still valid after the
user hits "clear form".
// Check to make sure we have a signed_request object, if not, redirect to home
var sreq = Request.Form["signed_request"];
if (string.IsNullOrEmpty(sreq))
{
Response.Redirect(WebConstants.SiteConstants.Home);
}
var app = new FacebookApp();
WHy is app.UserId still populated if the user clears the FORM!
How do I detect that we really want to integrate with FB or not ?
thanks!
I agree about the authentication vs registration but I think the Facebook API is not clearing the authentication cookie correctly because it is still valid if you clear the form or logout (i'm using iFrame registration), so I guess I'm looking for a best practice since when I get the signed-request, the id is the authenticated user so the only work-around I have right now is to check for String.IsEmptyOrNull( on the password field) - which tells me that this user did not use facebook registration). I think this is a hack but if anyone knows the "proper" way to take a signed request and convert it to an object please comment on my approach. It is kinda amazing that we still have to write a ton of code for what should be a straight forward approach to getting what we need. I've seen tons of complaints about FB development and they are mostly true - the Google API is not this frustrating and FB makes it almost impossible to test different environments as well (not to mention the famous cookie problems with localhost).
Problem : App.UserId is still the authenticated user after clearing the iframe form or logging out of FB - go figure.
Solution : check the presense of password field - this tells us that we have a non-fb registration going on....
C#.NET for all the minority out there.
// Check to make sure we have a signed_request object, if not, redirect to home var sreq = Request.Form["signed_request"]; if (string.IsNullOrEmpty(sreq)) { Response.Redirect(WebConstants.SiteConstants.Home); }
JObject meObject = null;
var app = new FacebookApp();
if (app.SignedRequest != null)
{
meObject = JObject.Parse(app.SignedRequest.Dictionary["registration"].ToString());
// Access meObject
JavaScriptSerializer ser = new JavaScriptSerializer();
fbReg = ser.Deserialize<FBRegistration>(meObject.ToString());
if (fbReg != null)
{
// 02 Feb 2011 - MCS - bug in facebook API, does not delete cookie if logout of FB
// We check to see if we have password - then - we know we can check the UserId
if (String.IsNullOrEmpty(fbReg.password))
{
// FB Registration
FacebookUserId = app.UserId;
}
else
{
FacebookUserId = 0;
// Non FB Registration
Registration and authentication are two completely different things. Just because the user "clears" a form does not log them out of facebook. The C# SDK is just detecting if a signed_request exists and is valid.