So i'm building a site backend for a project using strapi. A requirement for the site backend is there has to be a e-commerce component included since the client wants to do order processing, and process credit card transactions using stripe. With the that being said I decided to write a custom controller for user accounts that are created under the user-permissions plugin for strapi. This way I could link the user accounts from strapi to corresponding customer accounts on stripe's end.
However, i'm running into one problem in particular. The custom user controller methods I have written for creating and updating users seem to work as intended. Though whenever I delete users strapi doesn't seem to use the custom method I have written for deleting users at all. I even included a console.log call but nothing pops up.
I'm not really sure on how to go about fixing this issue since I can't see any of the log calls, and strapi doesn't seem to be spitting out any errors either when this occurs. Any advice on how to go about resolving this issue is appreciated.
Here's the custom controller i'm working on.
<project_dir>/extensions/user-permissions/controllers/User.js
'use strict';
/**
* A set of functions called "actions" for `user`
*/
const _ = require('lodash'),
{ sanitizeEntity } = require('strapi-utils'),
stripe = require('stripe')('<SK_HERE>'),
sanitizeUser = (user) => sanitizeEntity(user, {model: strapi.query('user', 'users-permissions').model}),
formatError = (error) => [{ messages: [{ id: error.id, message: error.message, field: error.field }] }];
module.exports = {
async create(ctx) {
const advanced = await strapi.store({
environment: '',
type: 'plugin',
name: 'users-permissions',
key: 'advanced'
}).get();
const { email, username, firstName, lastName, password, role } = ctx.request.body;
if (!email) return ctx.badRequest('missing.email');
if (!username) return ctx.badRequest('missing.username');
if (!password) return ctx.badRequest('missing.password');
if (!firstName) return ctx.badRequest('missing.firstName');
if (!lastName) return ctx.badRequest('missing.lastName');
const userWithSameUsername = await strapi
.query('user', 'users-permissions')
.findOne({ username });
if (userWithSameUsername) {
return ctx.badRequest(
null,
formatError({
id: 'Auth.form.error.username.taken',
message: 'Username already taken.',
field: ['username'],
})
);
}
if (advanced.unique_email) {
const userWithSameEmail = await strapi
.query('user', 'users-permissions')
.findOne({ email: email.toLowerCase() });
if (userWithSameEmail) {
return ctx.badRequest(
null,
formatError({
id: 'Auth.form.error.email.taken',
message: 'Email already taken.',
field: ['email'],
})
);
}
}
const user = {
...ctx.request.body,
provider: 'local',
};
user.email = user.email.toLowerCase();
if (!role) {
const defaultRole = await strapi
.query('role', 'users-permissions')
.findOne({ type: advanced.default_role }, []);
user.role = defaultRole.id;
}
try {
const customer = await stripe.customers.create({name: `${firstName} ${lastName}`, email: email});
user.stripeId = customer.id;
const data = await strapi.plugins['users-permissions'].services.user.add(user);
ctx.created(sanitizeUser(data));
} catch (error) {
ctx.badRequest(null, formatError(error));
}
},
async update(ctx) {
const advancedConfigs = await strapi.store({
environment: '',
type: 'plugin',
name: 'users-permissions',
key: 'advanced',
}).get();
const { id } = ctx.params;
const { email, username, password, firstName, lastName} = ctx.request.body;
const user = await strapi.plugins['users-permissions'].services.user.fetch({id});
if (_.has(ctx.request.body, 'email') && !email) {
return ctx.badRequest('email.notNull');
}
if (_.has(ctx.request.body, 'username') && !username) {
return ctx.badRequest('username.notNull');
}
if (_.has(ctx.request.body, 'firstName') && !firstName) {
return ctx.badRequest('firstName.notNull');
}
if (_.has(ctx.request.body, 'lastName') && !lastName) {
return ctx.badRequest('lastName.notNull');
}
if (_.has(ctx.request.body, 'password') && !password && user.provider === 'local') {
return ctx.badRequest('password.notNull');
}
if (_.has(ctx.request.body, 'username')) {
const userWithSameUsername = await strapi
.query('user', 'users-permissions')
.findOne({ username });
if (userWithSameUsername && userWithSameUsername.id != id) {
return ctx.badRequest(
null,
formatError({
id: 'Auth.form.error.username.taken',
message: 'username.alreadyTaken.',
field: ['username'],
})
);
}
}
if (_.has(ctx.request.body, 'email') && advancedConfigs.unique_email) {
const userWithSameEmail = await strapi
.query('user', 'users-permissions')
.findOne({ email: email.toLowerCase() });
if (userWithSameEmail && userWithSameEmail.id != id) {
return ctx.badRequest(
null,
formatError({
id: 'Auth.form.error.email.taken',
message: 'Email already taken',
field: ['email'],
})
);
}
ctx.request.body.email = ctx.request.body.email.toLowerCase();
}
let updateData = {
...ctx.request.body,
};
if (_.has(ctx.request.body, 'password') && password === user.password) {
delete updateData.password;
}
if(email != null || firstName != null || lastName != null) {
let stripeUpdate = {};
if(email != null && (email !== user.email)) stripeUpdate = {...stripeUpdate, email: email};
if((firstName != null && (firstName !== user.firstName)) || (lastName != null && (lastName !== user.lastName))) stripeUpdate = {
...stripeUpdate,
name: `${firstName != null && (firstName !== user.firstName) ? firstName : user.firstName} ${lastName != null && (lastName !== user.lastName) ? lastName : user.lastName}`
};
if(Object.keys(stripeUpdate).length > 0) {
const customerUpdate = await stripe.customers.update(user.stripeId, stripeUpdate);
}
}
const data = await strapi.plugins['users-permissions'].services.user.edit({ id }, updateData);
ctx.send(sanitizeUser(data));
},
async destroy(ctx) {
console.log('test...');
const { id } = ctx.params,
user = await strapi.plugins['users-permissions'].services.user.fetch({id}),
customerDelete = await stripe.customers.del(user.stripeId),
data = await strapi.plugins['users-permissions'].services.user.remove({id});
ctx.send(sanitizeUser(data));
}
};
Did you activate the destroy action in user-permissions?
I've tested it with this simple User.js in <project_dir>/extensions/user-permissions/controllers/:
module.exports = {
async find(ctx) {
console.log('find!!!');
return [];
},
async destroy(ctx) {
console.log('destroy!!!');
return [];
},
}
console.log('destroy!!!') is called when I send a DELETE request to http://localhost:1337/users/1234
Related
I'm working on the user registration functionality of an application using Typescript and Redux Toolkit. When I make the fetch request to the signup endpoint, a new user is saved to the database I've connected, but I keep entering the catch error block.
export const registerUser = createAsyncThunk(
"user/registerUser",
async (form: { username:string, password:string }, thunkAPI) => {
try {
const response = await fetch('/api/auth/signup', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
userInfo: {
username: form.username,
password: form.password
}
}),
});
if (response.status === 200) return 200;
else return 400;
} catch (e) {
console.log('Error')
}
}
);
I've tried logging the response to the console a number of ways
const data = await response.json() console.log(data)
But have had no luck. I think this is an error with how I've done my fetch request using createAsyncThunk but haven't been able to figure out what I've missed.
This is the code for the initial state and slice:
interface UserState {
userProfile: {
id: number | null;
},
registration: {
status: 'loaded' | 'loading'
}
}
const initialState : UserState = {
userProfile: {
id: null,
},
registration: {
status: 'loaded'
}
};
export const userSlice = createSlice({
name: 'user',
initialState,
reducers: {
},
extraReducers: (builder) => {
builder.addCase(registerUser.fulfilled, (state) => { state.registration.status = 'loaded' }),
builder.addCase(registerUser.rejected, (state) => { state.registration.status = 'loaded' }),
builder.addCase(registerUser.pending, (state) => { state.registration.status = 'loading' })
}
})
And here is the code for the function where the action is dispatched on the UI
const handleRegister= async () => {
if (!username) return alert('Username field was left empty');
if (!password) return alert('Password field was left empty');
const {payload} : any = await dispatch(registerUser({ username: username, password: password}));
if (payload === 200) {
alert('Successfully registered. Redirecting to dashboard');
return navigate('/dashboard');
} else { return alert('User creation unsuccessful'); }
}
Appreciate any help as I've looked through many other posts but haven't been able to resolve my issue.
I have a node.js application where I'm using the sign in functionality.
I'm facing an issue; that is, when I try to sign in, sometimes the request gets stuck. It won't return anything until I refresh the node.js server. Can anybody take a look at the code and help me out what am I doing wrong here?
Here is my Controller function
export const authenticate = async (
req: Request,
res: Response
): Promise<void> => {
try {
console.log("Login")
const result: DataObject = await model.authenticate(
req.body.username,
req.body.password
)
const { status, data } = result
res.status(status)
if(status==200) {
console.log(status)
const u : User = data as User;
const token = jwt.sign({ id: u.id }, process.env.JWT_SECRET as string)
res.json({token: token})
}
else {
res.json(data)
}
} catch (error) {
res.status(NOT_FOUND)
res.json(error)
}
}
And my Model method.
async authenticate(username: string, password: string): Promise<DataObject> {
try {
const sql =
"SELECT * FROM users WHERE username=$1"
const conn: PoolClient = await Client.connect()
const result: QueryResult<User> = await conn.query(sql, [username])
const { rows } = result
if (rows.length > 0) {
const user: User = rows[0]
const pepper: string = process.env.PASSWORD_HASH as string
const pepperedPass = password + pepper;
const validPass : boolean = bcrypt.compareSync(pepperedPass, user.password_digest);
if (validPass) {
const result: DataObject = {
status: OK,
data: user,
}
return result
}
else {
const passErr: DataObject = {
status: NOT_FOUND,
data: "Incorrect Password",
}
return passErr
}
}
const userErr: DataObject = {
status: NOT_FOUND,
data: "No User found with this Username",
}
return userErr
} catch (error) {
const userErr: DataObject = {
status: NOT_FOUND,
data: "No User found with this Username",
}
return userErr
throw new Error(`Not found, ${error}`)
}
}
I have tried res._destroy, res.set("Connection", "close"); but the issue persists.
Any suggestion/solution is appreciated.
Thank you.
Hi i am trying to setup LDAP authentication for my meteorJS app and i am following the steps listed in here https://janikvonrotz.ch/2017/02/08/meteor-register-ldap-login-request-handler/
i changed the search filter from mail to username and pushed everything inside of Meteor.startup() here is my code set up
UI code written in /imports/ui/loginform.jsx
let loginUserWithLDAP = (username, password, callback) => {
var loginRequest = {
ldap: true,
username: username,
email: username+"#company.com",
pass: password,
}
Accounts.callLoginMethod({
methodArguments: [loginRequest],
userCallback: callback
})
}
in my /server/ldap.js
Meteor.startup(() => {
var ldapAuth = {
url: 'ldap://company.com:389',
searchOu: 'ou=Employees,ou=\'company Users\', dc=company,dc=com',
searchQuery: (username) => {
return {
filter: '(&(objectclass=user)(samaccountname='+username+'))',
scope: 'sub'
}
}
}
ldapAuth.checkAccount = (options) => {
options = options || {}
ldapAuth.client = ldap.createClient({
url: ldapAuth.url
})
let dn = ['company', 'com']
var future = new Future()
ldapAuth.client.search(
ldapAuth.searchOu,
ldapAuth.searchQuery(options.username),
(error, result)=> {
assert.ifError(error)
result.on('searchEntry', (entry) => {
dn.push(entry.objectName)
return ldapAuth.profile = {
firstname: entry.object.cn,
lastname: entry.object.sn
}
})
result.on('error', function(error){
throw new Meteor.Error(500, "LDAP server error")
})
result.on('end', function(){
if (dn.length === 0) {
future['return'](false)
return false
}
return ldapAuth.client.bind(dn[0], options.pass, (error) =>{
if (error){
future['return'](false)
return false
}
return ldapAuth.client.unbind((error) => {
assert.ifError(error)
return future['return'](!error)
});
})
})
})
return future.wait()
}
Accounts.registerLoginHandler('ldap', (loginRequest)=>{
if (!loginRequest.ldap) {
return undefined
}
if (ldapAuth.checkAccount(loginRequest)){
var userId = null
var user = Meteor.users.findOne({"username": loginRequest.username })
if (!user) {
userId = Accounts.createUser({
username: loginRequest.username,
password: loginRequest.pass,
profile: ldapAuth.profile,
roles: ['user'],
})
Meteor.users.update(userId, { $set: { 'emails.0.verified': true } })
} else {
userId = user._id
}
let stampedToken = Accounts._generateStampedLoginToken()
let hashStampedToken = Accounts._hashStampedToken(stampedToken)
Meteor.users.update(userId,
{ $push: {'services.resume.loginTokens': hashStampedToken } }
)
return {
userId: userId,
token: stampedToken.token
}
}
})
});
In my debugging i found that its erroring out at
result.on('error', function(error){
throw new Meteor.Error(500, "LDAP server error")
})
due to '000004DC: LdapErr: DSID-0C0907E9, comment: In order to perform this operation a successful bind must be completed on the connection., data 0, v2580' what does this mean?
What is my code missing?
In short you need to define a search user that does the binding with the LDAP directory.
The post is outdated, I've got you this example: https://github.com/janikvonrotz/Zenkom/blob/0583f01abca96847178a248ff446d84c754965e9/server/actions/ldap.js#L18
Setup the search user like this:
"searchUser": {
"dn": "CN=username,OU=org,DC=company,DC=ch",
"password": "password"
}
The bind user is simply for search the directory. Another bind is executed to authenticate the found user.
I am new to NuxtJS and have a problem, I am trying to get the token of the user who logged in from the website, the token is stored in the cookie, but, when I start or reload the website page (made with nuxt ssr (universal )), nuxtServerInit should and does start, but req.headers.cookie says it is undefined, so the data cannot be loaded into the store. If I reload the page, the cookie is still in the browser and everything is perfect, the problem is that the req.headers.cookie is: undefined, why? Ahhh, and in development it works perfectly, but in production it doesn't work (in nuxtServerInit the req.headers.cookie is not defined)
I am using Firebase Hosting, Cloud Features, Authentication, Cloud Firestore.
Here is the code:
// store/index.js
import { getUserFromCookie, getUserFromSession } from '../helpers/index.js'
export const actions = {
async nuxtServerInit({ app, dispatch }, { req, beforeNuxtRender }) {
console.log('req.headers.cookie: ' + req.headers.cookie)
console.log('req.session: ', req.session)
if (process.server) {
const user = getUserFromCookie(req)
console.log('process.server: ' + process.server)
console.log('user: ', user)
if (user) {
await dispatch('modules/user/setUSER', {
name: !!user.name ? user.name : '',
email: user.email,
avatar: !!user.picture ? user.picture : '',
uid: user.user_id
})
await dispatch('modules/user/saveUID', user.user_id)
} else {
await dispatch('modules/user/setUSER', null)
await dispatch('modules/user/saveUID', null)
}
}
}
}
// helpers/index.js
import jwtDecode from 'jwt-decode'
var cookieparser = require('cookieparser')
export function getUserFromCookie(req) {
if(req.headers.cookie){
const parsed = cookieparser.parse(req.headers.cookie)
const accessTokenCookie = parsed.access_token
if (!accessTokenCookie) return
const decodedToken = jwtDecode(accessTokenCookie)
if (!decodedToken) return
return decodedToken
}
return null
}
// pages/auth/signup.vue
<script>
import { mapActions } from 'vuex'
export default {
data () {
return {
email: '',
password: '',
renderSource: process.static ? 'static' : (process.server ? 'server' : 'client')
}
},
middleware: ['handle-login-route'],
methods: {
...mapActions('modules/user', [ 'login' ]),
async signUp () {
try {
const firebaseUser = await this.$firebase.auth().createUserWithEmailAndPassword(this.email, this.password)
await this.writeUserData(firebaseUser.user.uid, firebaseUser.user.email)
await this.login(firebaseUser.user)
this.$router.push('/protected')
} catch (error) {
console.log(error.message)
}
},
writeUserData (userId, email) {
const db = this.$firebase.firestore()
return db.collection("Usuarios").doc(userId).set({
email: email
})
}
}
}
</script>
// store/modules/user.js
export const actions = {
async login({ dispatch, state }, user) {
console.log('[STORE ACTIONS] - login')
const token = await this.$firebase.auth().currentUser.getIdToken(true)
const userInfo = {
name: user.displayName,
email: user.email,
avatar: user.photoURL,
uid: user.uid
}
Cookies.set('access_token', token) // saving token in cookie for server rendering
await dispatch('setUSER', userInfo)
await dispatch('saveUID', userInfo.uid)
console.log('[STORE ACTIONS] - in login, response:', status)
},
async logout({ commit, dispatch }) {
console.log('[STORE ACTIONS] - logout')
await this.$firebase.auth().signOut()
Cookies.remove('access_token');
commit('setUSER', null)
commit('saveUID', null)
},
saveUID({ commit }, uid) {
console.log('[STORE ACTIONS] - saveUID')
commit('saveUID', uid)
},
setUSER({ commit }, user) {
commit('setUSER', user)
}
}
Thanks a lot! :D
if you are hosting it on Firebase you should rename your cookie to "__session", that's the only name you could use on Firebase. Their documentation should make it very clear!
Change the following part:
// helpers/index.js
const parsed = cookieparser.parse(req.headers.cookie)
const accessTokenCookie = parsed.__session
// store/modules/user.js
Cookies.set('__session', token)
i want collect news from different channels and echo them in a second channel, with the code i can read channels(not all but most).
I stuck now on the echo problem and have no clue about how i can do this, mtproto is completely new to me, thanks.
Im using the following code i have from another stackoverflow question.
const { MTProto, getSRPParams } = require('#mtproto/core');
const prompts = require('prompts');
const api_id = 'xxxxx';
const api_hash = 'xxxxx';
async function getPhone() {
return (await prompts({
type: 'text',
name: 'phone',
message: 'Enter your phone number:'
})).phone
}
async function getCode() {
// you can implement your code fetching strategy here
return (await prompts({
type: 'text',
name: 'code',
message: 'Enter the code sent:',
})).code
}
async function getPassword() {
return (await prompts({
type: 'text',
name: 'password',
message: 'Enter Password:',
})).password
}
const mtproto = new MTProto({
api_id,
api_hash,
});
function startListener() {
console.log('[+] starting listener')
mtproto.updates.on('updates', ({ updates }) => {
const newChannelMessages = updates.filter((update) => update._ === 'updateNewChannelMessage').map(({ message }) => message) // filter `updateNewChannelMessage` types only and extract the 'message' object
for (const message of newChannelMessages) {
// printing new channel messages
console.log(`[${message.to_id.channel_id}] ${message.message}`)
}
});
}
// checking authentication status
mtproto
.call('users.getFullUser', {
id: {
_: 'inputUserSelf',
},
})
.then(startListener) // means the user is logged in -> so start the listener
.catch(async error => {
// The user is not logged in
console.log('[+] You must log in')
const phone_number = await getPhone()
mtproto.call('auth.sendCode', {
phone_number: phone_number,
settings: {
_: 'codeSettings',
},
})
.catch(error => {
if (error.error_message.includes('_MIGRATE_')) {
const [type, nextDcId] = error.error_message.split('_MIGRATE_');
mtproto.setDefaultDc(+nextDcId);
return sendCode(phone_number);
}
})
.then(async result => {
return mtproto.call('auth.signIn', {
phone_code: await getCode(),
phone_number: phone_number,
phone_code_hash: result.phone_code_hash,
});
})
.catch(error => {
if (error.error_message === 'SESSION_PASSWORD_NEEDED') {
return mtproto.call('account.getPassword').then(async result => {
const { srp_id, current_algo, srp_B } = result;
const { salt1, salt2, g, p } = current_algo;
const { A, M1 } = await getSRPParams({
g,
p,
salt1,
salt2,
gB: srp_B,
password: await getPassword(),
});
return mtproto.call('auth.checkPassword', {
password: {
_: 'inputCheckPasswordSRP',
srp_id,
A,
M1,
},
});
});
}
})
.then(result => {
console.log('[+] successfully authenticated');
// start listener since the user has logged in now
startListener()
});
})