I am using anonymous auth to allow my users to use the app without logging in. However, Firebase seems to persist these anonymous user IDs indefinitely. Is there a way to automatically purge these or set some sort of expiration rule? I don't want these one-time use IDs to live forever and clutter the actual user data from providers.
Unfortunately this is a "memory leak" (user leak?) Since there is no reasonable way to force an anonymous user to convert, these anonymous user ids will soon become zombies that serve no particular purpose (that I can think of). Furthermore, a single real user might (forgetfully) sign in as an anonymous user, yet again, after already linking their email to a previous incarnation, and then get frustrated when trying to link to their email. Overall, I find the current anonymous user implementation impractical, or at very least far from ideal.
For now I am planning to have an email address which is random but unique for a given user/device for signing in anonymous users, instead of using the builtin anonymous signin (which is disabled). In my opinion there needs to be a setting to tell Firebase to delete an anonymous user id upon sign out (they are useless at that point anyway) and/or after a predefined amount of time. In addition, it might be useful to be able to sign in again, with the same anonymous user id, until the expiration time (like by saving a token/etc.) Lastly, an attempt to link an email that is already in use should just merge the anonymous user id with the existing email/password user id through a verification step.
Somehow, there is a way of delete old anonymous users. I do it with a AppEngine cronjob that runs hourly.
But before you do that you have to define, what a anonymous user is. My users have to validate their email address and therefore I declare all users who are not validated to be anonymously after 90 days.
With the PubSub tick I then collect all users and delete them, here you've got a sample:
export const removeOldUsers = functions.pubsub.topic( "hourly-tick" ).onPublish( event => {
function getInactiveUsers( users: Array<UserRecord> = [], nextPageToken?: string ) {
let userList = users;
return admin.auth().listUsers( 1000, nextPageToken ).then( ( result: any ) => {
console.log( `Found ${result.users.length} users` );
const inactiveUsers = result.users.filter( ( user ) => {
return moment( user.metadata.lastSignInTime ).isBefore( moment().subtract( 90, "days" ) ) && !user.emailVerified;
} );
console.log( `Found ${inactiveUsers.length} inactive users` );
// Concat with list of previously found inactive users if there was more than 1000 users.
userList = userList.concat( inactiveUsers );
// If there are more users to fetch we fetch them.
if ( result.pageToken) {
return getInactiveUsers( userList, result.pageToken );
}
return userList;
} );
}
return new Promise( ( resolve ) => {
console.info( `Start deleting user accounts` );
getInactiveUsers().then( ( users ) => {
resolve( users );
} );
} ).then( ( users: Array<UserRecord> ) => {
console.info( `Start deleting ${users.length} user accounts` );
return Promise.map( users, ( user ) => {
return admin.auth().deleteUser( user.uid ).then( () => {
console.log( "Deleted user account", user.uid, "because of inactivity" );
} ).catch( ( error ) => {
console.error( "Deletion of inactive user account", user.uid, "failed:", error );
} );
}, { concurrency: 3 } );
} ).then( () => {
console.info( `Done deleting user accounts` );
} );
} );
Here I just pushed my class to npmjs #beyond-agentur-ug/firebase-delete-inactive-users
There is no way to bulk-delete, however, the following trick worked for me:
I used Macro Recorder and it worked like a charm. Just recorded a few iterations in the console of me deleting users, set it to repeat 500 times and walked away.
You can use Firebase's admin API to delete users programatically. You'll need to store a user list in your database as Firebase doesn't provide a query for that.
Managing users
Anonymous users can be a starting point before you upgrade them to a non anonymous user (think of an e-commerce site where an anonymous user adds stuff into his cart and then on checkout, upgrades to a Google or email/password user; in this case you probably do not want to lose the user's cart). As explained, this could be useful if you want to persist data from an anonymous user to an upgraded user. If you wish to purge anonymous users, there is no automated way to do so. However as soon as you either sign out the anonymous user or sign in a non anonymous user, the state of the anonymous user will be lost.
Related
On my platform, the administrator create a user where the password is randomly generated and this automatically sends an email to this new user. The email contains a link that leads to the reset-password page (which will be a password creation page for the user because he does not know that he already has a password generated).
The problem is that when the user clicks on the email link and arrives on the change password page, he is logged in as admin and therefore has permissions that he should not have.
In fact, I want the email link to connect the new user to his account, I don't want him to be logged in as admin. I'm not sure how to do this.
I don't know much about tokens. I believe the Token is generated based on the session used (?).
Thank you in advance for your help.
Here is the code for creating a user :
/**
* #Route("/new", name="user_new", methods={"GET", "POST"})
* #throws TransportExceptionInterface
*/
public function new(Request $request, MailSender $mailSender,UserPasswordHasherInterface $passwordHasher): Response
{
// TODO CHECK IF USER ALREADY EXISTS BY EMAIL
$user = new User();
$form = $this
->createForm(UserType::class, $user)
->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// TODO GENERATE RANDOM PASSWORD
//$passwordHasher->hashPassword($user, $user->getPassword()));
$user->setPassword($passwordHasher->hashPassword($user, "password"));
$this->entityManager->persist($user);
$this->entityManager->flush();
try {
$resetToken = $mailSender->makeToken($user);
} catch (ResetPasswordExceptionInterface $e) {
return $this->redirectToRoute('user_new');
}
$mailInfos = array('template'=>"reset_password/email_activate.html.twig", 'subject'=>"Activer votre compte", 'email'=>$user->getEmail());
$mailSender->sendMail($resetToken, $mailInfos);
$mailSender->storeToken($resetToken);
return $this->redirectToRoute('user_index', [], Response::HTTP_SEE_OTHER);
}
return $this->renderForm('user/new.html.twig', [
'user' => $user,
'form' => $form,
]);
}
This is expected behaviour because:
multiple tabs/instances of the same browser will usually share the
same server-side session when interacting with the same domain.
means that you can´t be logged in with different users in different tabs per default.
And I don´t think that you would want this, just think of the downsides, do you really want to login again for every tab? This is very uncommon practice. Imagine you would open a stack-overflow question in a new tab and you would not be logged in there.
There are ways to achieve this though, but really re-think if thats your actual usecase, i don´t think so, you are just developing your feature and testing it, and in production a new user will not be already logged in as admin is my assumption.
So for testing your feature just use a private tab (that does usually not share the same server-side session )
if you want to learn more i found this pretty cool so-thread where users try to explain as best as possible
What are sessions? How do they work?
I want that user having special role assigned can buy product from my store without pay. I searched around and found Role based shipping/payment plugin. This plugin works fine for other user role but it do not allow user having special role to place order.
Is there a way to allow only users with special role to place order without any payment and shipping method?
Thanks in advance.
You can modify price by this function.Please take backup and perform modifications.
public function get_db_price($id){
$user = wp_get_current_user();
if($user->roles[0] == "VIP"){
return 0;
}
else{
self::$db_prices = get_post_meta($id,'_role_based_price',true );
if(is_array(self::$db_prices)){
return self::$db_prices;
}
}
return false;
}
I am currently using the client-API to implement a simple user front-end to upload products. The function client->products->create() seems to work fine, how ever I can’t get around one issue.
Every time I upload a product, the vendor is set to the admin user instead of the user that is currently logged in. Is there a way to set the vendor through the API? Has anybody get done this?
This is the function I created that is called by AJaX when the form is submitted (I left key and website fields empty here on purpose):
function addProduct()
{
$options = array(
'debug' => false,
'return_as_array' => false,
'validate_url' => false,
'timeout' => 30,
'ssl_verify' => false,
);
try {
$client = new WC_API_Client('', '', '', $options);
$productName = $_POST["productname"];
$price = $_POST["price"];
$discountPrice = $_POST["discountPrice"];
$description = $_POST["description"];
$shortDescription = $_POST["shortDescription"];
$authorId = 5;
$client->products->create(array('title' => $productName, 'type' => 'simple', 'regular_price' => $price, 'description' => $description));
} catch (WC_API_Client_Exception $e) {
echo $e->getMessage() . PHP_EOL;
echo $e->getCode() . PHP_EOL;
if ($e instanceof WC_API_Client_HTTP_Exception) {
print_r($e->get_request());
print_r($e->get_response());
}
}
echo ("Publicado" . $authorId);
// Una función AJaX en WordPress debe siempre terminarse con die().
die();
}
The problem seems to be the consumer key and consumer secret, so, is there a way to programmatically provide the clients with API keys and get these dynamically?
UPDATE: The method to obtain the consumer key described below will not work; it is no longer possible to get hold of the consumer key from the database once it has been generated. The consumer key stored in this new table is not the same consumer key that is generated in the admin screens and passed out to the end user. It appears to be an SHA256 hashed a version of this key. This is more secure (previously the consumer key and secret stored in wp_usermeta was tantamount to storing clear-text passwords, as anyone with access to that data would be able to log into the API as any of those users), but is a little less convenient. Win some, lose some, but win on security.
Your new WC_API_Client() will take three parameters before the options: $store_url, $consumer_key and $consumer_secret.
Any user on the WC shop who is to be used to access the API will need a consumer key or consumer secret. The consumer key will identify which user the API will run as, and it is that user which will be linked to any entities created through the API.
Until recently, you could get these two pieces of information for a user like this:
$consumer_key = get_user_meta($user_id, 'woocommerce_api_consumer_key', true);
$consumer_secret = get_user_meta($user_id, 'woocommerce_api_consumer_secret', true);
Where $user_id is the ID for the user that will be creating items. If you want the current logged in user to be able to create items in their name then that user would need to be given a consumer key and secret, and would need to be in an appropriate WC/WP group to give them permission to do so.
Note, that if you do this, then the user will also have access to the admin pages for WC to create these items, and not just through the API.
In later versions of WC, the user meta items have been moved to a separate table: wp_woocommerce_api_keys so you need to look in there instead of in the user meta.
This will get you the consumer key and secret for a given user ID:
global $wpdb;
$key = $wpdb->get_row( $wpdb->prepare("
SELECT consumer_key, consumer_secret, permissions
FROM {$wpdb->prefix}woocommerce_api_keys
WHERE user_id = %d
", $user_id), ARRAY_A);
the results being something like this:
array(3) {
["consumer_key"]=>
string(64) "58043812eee6aa75c80407f8eb9cec025825f138eb7d60118af66cf4b38060fa"
["consumer_secret"]=>
string(43) "cs_1da716412bb9680d8b06b09160872b7e54416799"
["permissions"]=>
string(10) "read_write"
}
I am, of course, assuming you are using the API to "loop back" to the current site and not accessing a remote site. Using the WC API to create products even on the current site can be very much more convenient than going through the PHP object API.
I have not yet found any public WC methods to get these details; they are all private and assume only WC needs to know these details.
Yes there is a fine customization that you need to do in your code that is as follows:
Background information:
Each users Consumer Key,Consumer Secret Key and read/write permissions (if WooCommerce API Keys are generated for that users) are stored in wordpress's usermeta table with a meta_keys as 'woocommerce_api_consumer_key', 'woocommerce_api_consumer_secret' and 'woocommerce_api_key_permissions' respectively.
So you just need to get the current users id first then get that user's meta value as mention above assign to some variables and send them as a parameter.
I think the problem is generate programmatically the API keys for that customer for witch you want consume the woocommerce service, because the keys ar owned for each users and there aren't be useful for other users.
My advice is looking admin source code of woocommerce.
I'm using Formidable forms in Wordpress and have a form that registers users. I can use a radio button in the registration form to determine what their role will be. I have a hook for that. What I need, however, is a hook that will change the user role based on radio selection on form entry UPDATE. My current code only works on entry creation. Here is the code that assigns roles on registration:
add_filter('frmreg_new_role', 'frmreg_new_role', 10, 2);
function frmreg_new_role($role, $atts){
extract($atts);
if($form->id == 8){
if($_POST['item_meta'][280] == 'Job Applicant')
$role = 'applicant';
}
return $role;
}
"8" is the id of the form itself. "280" is the id of the radio button field where "Job Applicant" is one of the values. And "applicant" is one of our site's user roles.
I need an adaptation of this that will change the role after the entry has already been created, on update. The closest thing I can find is a hook that changes user role after a successful PayPal payment. I tried to combine the two but I couldn't get it to work. Here is the PayPal generated user role changer:
add_action('frm_payment_paypal_ipn', 'change_paid_user_role');
function change_paid_user_role($args){
$new_role = 'contributor'; //change this to the role paid users should have
if(!$args['pay_vars']['completed'])
return; //don't continue if the payment was not completed
if(!$args['entry']->user_id or !is_numeric($args['entry']->user_id))
return; //don't continue if not linked to a user
$user = get_userdata($args['entry']->user_id);
if(!$user)
return; //don't continue if user doesn't exist
$updated_user = (array)$user;
// Get the highest/primary role for this user
$user_roles = $user->roles;
$user_role = array_shift($user_roles);
if ( $user_role == 'administrator' )
return; //make sure we don't downgrade any admins
$updated_user['role'] = $new_role;
wp_update_user($updated_user);
}
UPDATE: the action hook should probably be: frm_after_create_entry according to Formidable forums.
Many times, researching the core files is more productive than any Google or Manual. Dropping the whole plugin directory in a code editor and researching for the string frm_after_create_entry takes us to the create() method where this hook happens.
After that, there's the update() method and it provides the action hook: frm_after_update_entry.
This hook passes two parameters: $id and $new_values['form_id']. I cannot reproduce your setup, so testing the hook is up to you.
Reference: Actions and filters are NOT the same thing…
In this example:
add_action( 'frm_after_update_entry', 'change_role_to_staff', 10, 2);
function change_role_to_staff( $form_id, $values ){
var_dump($values);
die();
}
As this is an action hook, nothing has to be returned.
There's no $roles or $atts, the parameters are the form ID and Values.
What you're looking for is inside $values.
var_dump() and die() are for debugging purposes and must be removed at once after testing.
Do your wp_update_user with this values and adapting your previous code.
I may be missing something blindingly obvious here (I hope so).....I am creating a module in Drupal 6 that consists of some triggers and actions. in it's simplest form it consists of:
An action which checks for some criteria (event that needs to be triggered once a month per user)
A trigger which is fired for each user that the criteria is true for
I would like as much as possible to be managed through the triggers / actions interface in Drupal as the site admin is not a developer. The plan is to use the cron trigger to fire the action in 1. which will then fire a trigger for each user. The site admin will then be able to create a Send Email action through the actions interface and hook it up to the trigger from 2.
The part I can't get my head around is how the recipient of the email will be specified - the user trigger will be fired from an action run by cron (i.e. not in any user context) - how can I pass in a variable that can be used here?
Thanks,
Triggers fire actions not the other way around.
The user that you pass to actions_do dosn't have to be the logged in user. You can query for the users that you want to email and loop thrhough them doing user_load and then an actions_do
something like
foreach ($user_ids as $uid) {
$context_user = user_load(array('uid' => $uid));
$context = array(
'hook' => 'myhook',
'op' => $op,
'user' => $context_user,
);
actions_do(array_keys($aids), $context_user, $context);
}