Since Microsoft disabled Basic authentication, I need to change this project for using OAuth and I cannot get it to work. Any help would be greatly appreciated.
old code:
// expose our config directly to our application using module.exports
module.exports = {
// this user MUST have full access to all the room accounts
'exchange' : {
'username' : process.env.USERNAME || 'SVCACCT_EMAIL#DOMAIN.COM',
'password' : process.env.PASSWORD || 'PASSWORD',
'uri' : 'https://outlook.office365.com/EWS/Exchange.asmx'
},
// Ex: CONTOSO.COM, Contoso.com, Contoso.co.uk, etc.
'domain' : process.env.DOMAIN || 'DOMAIN.COM'
};
module.exports = function (callback) {
// modules -------------------------------------------------------------------
var ews = require("ews-javascript-api");
var auth = require("../../config/auth.js");
// ews -----------------------------------------------------------------------
var exch = new ews.ExchangeService(ews.ExchangeVersion.Exchange2016);
exch.Credentials = new ews.ExchangeCredentials(auth.exchange.username, auth.exchange.password);
exch.Url = new ews.Uri(auth.exchange.uri);
// get roomlists from EWS and return sorted array of room list names
exch.GetRoomLists().then((lists) => {
var roomLists = [];
lists.items.forEach(function (item, i, array) {
roomLists.push(item.Name);
});
callback(null, roomLists.sort());
}, (err) => {
callback(err, null);
});
};
I recently went into the exactly situation and finally got it working after spending countless hours. Hope this post will help some lost souls like I was.
So, what happened? Basic auth to MS Exchange Online will be disabled by end of 2022. All relevant applications' authentication will require updates.
reference: https://techcommunity.microsoft.com/t5/exchange-team-blog/basic-authentication-deprecation-in-exchange-online-september/ba-p/3609437
How to do it? My use case is a simple one. A mail daemon application 1) logins and 2) download some email attachments. What happens in the background and what do you need to do are written in below article.
reference: https://learn.microsoft.com/en-us/exchange/client-developer/legacy-protocols/how-to-authenticate-an-imap-pop-smtp-application-by-using-oauth
In summary, you need to follow below steps:
Register your application with Azure Active Directory
Get an access token
Add required permissions to your app. If you are using plain password for your old project, you may refer to Use client credentials grant flow to authenticate IMAP and POP connections section in the above article. Below are a list of permissions required for my simple app. I added permission regarding email sending for future use:
Microsoft Graph:
IMAP.AccessAsUser.All
offline_access
openid
POP.AccessAsUser.All
profile
SMTP.Send
Office 365 Exchange Online:
full_access_as_app
IMAP.AccessAsApp
Mail.Read
Mail.ReadWrite
Mail.Send
Get tenant admin consent to your app (done by your Azure admin).
Register service principals in Exchange (done by your Azure admin).
This blog will talk you through above procedures:
https://blog.hametbenoit.info/2022/07/01/exchange-online-use-oauth-to-authenticate-when-using-imap-pop-or-smtp-protocol/#.Y6RdVXZBxm7
Authentication failed? you may be able to retrieve a token from Exchange server, but got an error message:"A1 NO AUTHENTICATE failed" when trying to connect to Exchange server. If you took above steps one by one, it was most likely to be a permission related issue, please refer to the list in step 3. Unfortunately, this is where took me longest to test and Exchange server does not provide more information than "you are screwed", which is a pity.
Last but not the least... Here is my sample Java code. The simple app authenticate with Exchange server using IMAP. Apache HttpCore and Jackson libraries are required.
1 - Class for access token generation:
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
public class OAuthDL {
public String getAuthToken() {
String token = "";
HttpClient httpClient = HttpClients.createDefault();
String tenant_id = "from Azure portal";
String client_id = "from Azure portal";
String client_pw = "created after app was registered";
String scope = "https://outlook.office365.com/.default";
HttpPost httpPost = new HttpPost("https://login.microsoftonline.com/" + tenant_id + "/oauth2/v2.0/token");
List<NameValuePair> params = new ArrayList<NameValuePair>();
params.add(new BasicNameValuePair("grant_type", "client_credentials"));
params.add(new BasicNameValuePair("client_id", client_id));
params.add(new BasicNameValuePair("client_secret", client_pw));
params.add(new BasicNameValuePair("scope", scope));;
try {
httpPost.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
try {
HttpResponse response = httpClient.execute(httpPost);
HttpEntity respEntity = response.getEntity();
if (respEntity != null) {
String content = EntityUtils.toString(respEntity);
ObjectNode node = new ObjectMapper().readValue(content, ObjectNode.class);
if (node.has("access_token")) {
token = node.get("access_token").asText();
}
}
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return(token);
}
public static void main(String[] args) {
OAuthDL oa = new OAuthDL();
String token = oa.getAuthToken();
System.out.println("Token: " + token);
}
}
Class that configures protocols and authenticate with Exchange server. JavaMail is required:
import java.util.Properties;
import javax.mail.Folder;
import javax.mail.Session;
import javax.mail.Store;
public class ImapMailBoxReader {
private String host;
private String username;
private String password;
public ImapMailBoxReader(String host, String username, String password) {
this.host = host;
this.username = username;
this.password = password;
}
public void testConnection(String folder) {
try {
String SSL_FACTORY = "javax.net.ssl.SSLSocketFactory";
Properties props= new Properties();
props.put("mail.imaps.ssl.enable", "true");
props.put("mail.imaps.sasl.enable", "true");
props.put("mail.imaps.port", "993");
props.put("mail.imaps.auth.mechanisms", "XOAUTH2");
props.put("mail.imaps.sasl.mechanisms", "XOAUTH2");
props.put("mail.imaps.auth.login.disable", "true");
props.put("mail.imaps.auth.plain.disable", "true");
props.setProperty("mail.imaps.socketFactory.class", SSL_FACTORY);
props.setProperty("mail.imaps.socketFactory.fallback", "false");
props.setProperty("mail.imaps.socketFactory.port", "993");
props.setProperty("mail.imaps.starttls.enable", "true");
props.put("mail.debug", "true");
props.put("mail.debug.auth", "true");
Session session = Session.getDefaultInstance(props, null);
session.setDebug(true);
Store store = session.getStore("imaps");
store.connect(host, username, password);
Folder inbox = store.getFolder(folder);
inbox.open(Folder.READ_ONLY);
inbox.close(false);
store.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
String host = "outlook.office365.com";
String username = "your email address";
OAuthDL oa = new OAuthDL();
String password = oa.getAuthToken();
ImapMailBoxReader reader = new ImapMailBoxReader(host, username, password);
reader.testConnection("inbox");
}
}
Hope this helps.
Related
I want when user click to admin profile, user can see the admin info.
Im using the currentUser to get the id of current user who logged in already in applications.
I'm trying to know how get the another user data.
String currentuser = FirebaseAuth.getInstance().getCurrentUser().getUid();
// init firebase database
mUserDatabaseReference = FirebaseDatabase.getInstance().getReference("Users");
mUserDatabaseReference.child(currentuser).addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
// here using Picasso for get the image url and set in ImageView
String imageUrl = dataSnapshot.child("profile_pic").getValue().toString();
Picasso.with(AboutCompany_for_users.this).load(imageUrl).into(companyPhoto);
String name = dataSnapshot.child("name").getValue().toString();
String email = dataSnapshot.child("email").getValue().toString();
String phone = dataSnapshot.child("mobile").getValue().toString();
String busesNumbers = dataSnapshot.child("buses_numbers").getValue().toString();
String lineName = dataSnapshot.child("bus_line").getValue().toString();
// here we get the data from
companyName.setText(name);
companyEmail.setText(email);
companyPhone.setText(phone);
companyBusesNumbers.setText(busesNumbers);
companyLineName.setText(lineName);
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
driversInformations.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent driversInfo = new Intent(AboutCompany_for_users.this, CompanyDrivers.class);
startActivity(driversInfo);
}
});
I expect when user click on admin profile show the admin info not current user info
You can't query other user accounts in Firebase Authentication from client code. You will have to either
Store the other user's data in a database, and query that database.
Call some backend code and use the Firebase Admin SDK to perform the query, and return that to the client.
Usually people choose option 1.
Create a Cloud Functions and use the Firebase Admin SDK.
You can get another user data by uid or email or phone number.
And you can list user each by max 1000.
If you create a Cloud Functions using node.js then code is like this.
// get another user data by uid
exports.getUser = functions.https.onCall(async (data, context) => {
try {
// if you want to deny unauthorized user
// - context.auth.token.xxx is customClaims. (if you use)
// if (context.auth || context.auth.uid || context.auth.token.xxx) {
// return Promise.reject("unauthorized");
// }
const user = await admin.auth().getUser(data.uid);
return {
displayName: user.displayName,
email: user.email,
photoURL: user.photoURL
};
} catch (error) {
// If user does not exist
if (error.code === "auth/user-not-found") {
return {};
}
throw error;
}
});
See:
https://firebase.google.com/docs/functions/callable
https://firebase.google.com/docs/auth/admin/manage-users#retrieve_user_data
You can create a Firebase cloud functions and you can call that function from your Android app
Next, under that function, you can retrieve the data of the corresponding user and return it as a result to your Android app.
On the frontend code, I have a user login form that takes in the values (strings) email and password. In my userstore using MobX State Management, I have an action when a user presses the login button to submit the strings as an HTTP post
#action login = async (values: IUserFormValues) => {
try {
console.log(values);
const user = await agent.User.login(values);
runInAction(() => {
this.user = user;
});
console.log(user);
} catch (error) {
console.log(error);
}
};
}
The Request looks something like this:
const responseBody = (response: AxiosResponse) => response.data;
const requests = {
post: (url: string, body: {}) =>
axios.post(url, body).then(sleep(1000)).then(responseBody),
};
login: (user: IUserFormValues): Promise<IUser> =>
requests.post(`/user/login`, user)
Now to the backend, this is where I am completely lost. Not sure what to build from here:
[HttpPost("login")]
- Here -
I am gonna have to take these values and verify with a database from SQL server. There's just so many different examples using different middleware that I am just not sure what's correct or best practices.
Here is a very nice tutorial from the Microsoft Docs, using Entity Framework (very nice), and some dependency injection (very very nice).
Basically you create an API controller class with your CRUD methods in them like so:
namespace MyApiControllerClass
{
[Authorize]
[RoutePrefix("users")]
public class UsersApiController : ControllerBase
{
private readonly UserContext _context;
public UsersApiController(UserContext context)
{
_context = context;
}
[Route("/login")]
public IHttpActionResult LoginUser(User user)
{
try
{
// login logic here
return Ok(); // you can return whatever you need
}
catch (Exception exception)
{
// log any issues using your preferred method of logging
return InternalServerError(); // you can return different status codes as well. Depends on what you want
}
}
}
}
You can read more about the Authorize annotation here and customize it to your liking.
Then you fire up your web project which will be available at a local URL that you can set in the project's configuration say http://localhost:4000/ which then makes your controller URL available at http://localhost:34501/users/login. Then you use this URL in your Javascript call and add the User object in the request body.
I am trying to implement a spring boot chat application using WebSocket stomp client. If I send messages from one device to 4,5 devices then some are getting the messages and some are not. Some can send messages but don't receive any message and some are working completely fine. My application is running on wildfly server and the URL is over https.
Here is my js file. From my JSP page I am calling sendMsg with all parameter and through render method I am attaching the response with JSP using Handlebars.
if (!window.location.origin) {
window.location.origin = window.location.protocol + "//" + window.location.hostname + (window.location.port ? ':' + window.location.port: '');
}
const url = window.location.origin+contextPath;
let stompClient;
let selectedUser;
let newMessages = new Map();
function connectToChat(userName, topicName) {
console.log("connecting to chat...")
let socket = new SockJS(url + '/chat');
stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
console.log("connected to: " + frame);
stompClient.subscribe("/topic/decision-log", function (response) {
let data = JSON.parse(response.body);
var msg = data.message;
var fromlogin = data.message;
render(data.username, msg, fromlogin);
});
});
}
connectToChat("1", "decision-log");
function sendMsg(from, text, username) {
stompClient.send("/app/chat/" + from, {}, JSON.stringify({
fromLogin: from,
message: text,
topicName: topicName,
username: username
}));
}
function render(username, message, projectId) {
var templateResponse = Handlebars.compile($("#message-response-template").html());
var contextResponse = {
username: username,
response: message,
date: date,
projectId: projectId
};
setTimeout(function () {
$chatHistoryList.append(templateResponse(contextResponse));
scrollToBottom();
}.bind(this), 1500);
}
Here is my WebSocket configuration file:
#Configuration
#EnableWebSocketMessageBroker
public class WebsocketConfiguration implements WebSocketMessageBrokerConfigurer{
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/chat").setAllowedOrigins("*").withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/app").enableSimpleBroker("/topic");
}
}
This is the controller. I always save all messages on the database that are coming through WebSocket that's why I can be sure that all devices can send messages as they have been saved on the database.
#Controller
#AllArgsConstructor
public class MessageController {
#Autowired
private SimpMessagingTemplate simpMessagingTemplate;
private final DecisionLogService decisionLogService;
#MessageMapping("/chat/{to}")
public void sendMessage(#DestinationVariable String to, MessageModel message, Authentication authentication ) {
simpMessagingTemplate.convertAndSend("/topic/decision-log", message);
AuthResponse userDetails = (AuthResponse) authentication.getDetails();
DecisionLogCreateRequest decisionLogCreateRequest = new DecisionLogCreateRequest();
decisionLogCreateRequest.setDecision(message.getMessage());
decisionLogCreateRequest.setProjectId(to);
ServiceResponseExtended<Boolean> response = decisionLogService.addDecisionLog(userDetails.getAccessToken(), decisionLogCreateRequest);
}
}
I can not find anything similar this issue. Please help me with right information and suggestion, and if anyone faced same kind of problem please share with me.
The problem was solved after configuring RabbitMQ Stomp Broker as a message broker instead of SimpleBroker.
Current WebSocket configuration:
#Configuration
#EnableWebSocketMessageBroker
public class WebsocketConfiguration implements WebSocketMessageBrokerConfigurer{
#Value("${stomp-broker-relay-host}")
private String RELAY_HOST;
#Value("${stomp-broker-relay-port}")
private String RELAY_PORT;
#Value("${stomp-broker-login-user}")
private String USER;
#Value("${stomp-broker-login-pass}")
private String PASS;
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/chat").setAllowedOrigins("*").withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/app");
registry.enableStompBrokerRelay("/topic").setRelayHost(RELAY_HOST).setRelayPort(Integer.parseInt(RELAY_PORT)).setClientLogin(USER)
.setClientPasscode(PASS);
}
}
Reference:
https://medium.com/#rameez.s.shaikh/build-a-chat-application-using-spring-boot-websocket-rabbitmq-2b82c142f85a
https://www.javainuse.com/misc/rabbitmq-hello-world
I have a function in an Android app which sends a POST request to an HTTP triggered Cloud Function. Whenever I click the button once to send the message, Firebase registers the event twice on the Firebase console. My application is built in such a way that the button to send a message disappears after the first click, so I'm not accidentally double clicking the button, and when I step through the debugger, the function to send the POST request is only called once. Can you guys help me? I don't know much about Firebase and can't find good documentation or other questions like this.
Here's the method which sends a message to my FCM cloud function:
public void sendPushToSingleInstance(final Context activity, final String message, final String myId, final String theirId) {
final String url = URL_TO_CLOUD_FUNCTION;
StringRequest myReq = new StringRequest(Request.Method.POST, url,
new Response.Listener<String>() {
#Override
public void onResponse(String response) {
Toast.makeText(activity, "Success", Toast.LENGTH_SHORT).show();
}
},
new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
if (error.networkResponse != null)
Toast.makeText(activity, String.valueOf(error.networkResponse.statusCode), Toast.LENGTH_SHORT).show();
else
Toast.makeText(activity, "some error", Toast.LENGTH_SHORT).show();
}
}) {
#Override
public byte[] getBody() throws com.android.volley.AuthFailureError {
Map<String, String> rawParameters = new Hashtable<String, String>();
//not used
return new JSONObject(rawParameters).toString().getBytes();
};
public String getBodyContentType() {
return "application/json; charset=utf-8";
}
#Override
public Map<String, String> getHeaders() throws AuthFailureError {
HashMap<String, String> headers = new HashMap<String, String>();
headers.put("from", myId);
headers.put("message", message);
headers.put("sendingTo", theirId);
return headers;
}
};
Volley.newRequestQueue(activity).add(myReq);
}
My JavaScript takes the HTTP request, cuts it up and send the message to a topic which contains the other user's id (I did mean to do this verses sending to a specific device).
Here's the JavaScript for my Cloud Function:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.sendMessage = functions.https.onRequest((request, response) => {
var topicId = request.get('sendingTo');
var color = request.get('color');
var from = request.get('from')
console.log('tried to push notification');
const payload = {
notification: {
title: from,
body: color,
sound: "default"
},
};
const options = {
priority: "high",
timeToLive: 60 * 60 * 24
};
admin.messaging().sendToTopic(topicId, payload, options);
});
Finally, here are the logs:
firebase console logs
Which say that the function was called twice.
I've tried many links for answers such as the standard,
https://firebase.google.com/docs/functions/http-events
and many StackOverflow posts. I haven't seen anyone else with the same problem.
From #mohamadrabee, "this from the documentation 'Always end an HTTP function with send(), redirect(), or end(). Otherwise, your function might to continue to run and be forcibly terminated by the system.' see firebase.google.com/docs/functions/http-events "
I added:
response.end();
after:
admin.messaging().sendToTopic(topicId, payload, options);
EDIT: After inserting this code, I still get the problem roughly 7% of the time. I had to change response.end(); to:
if (response.status(200)) {
response.status(200).end();
} else {
response.end();
}
I haven't had any problems since.
I would like to ensure that my JSON Web tokens are revoked/expire after a configurable ammount of time and i have the following set up:
Security Filter:
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import yourwebproject2.service.UserService;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
/**
* #author: kameshr
*/
public class JWTTokenAuthFilter extends OncePerRequestFilter {
private static List<Pattern> AUTH_ROUTES = new ArrayList<>();
private static List<String> NO_AUTH_ROUTES = new ArrayList<>();
public static final String JWT_KEY = "JWT-TOKEN-SECRET";
static {
AUTH_ROUTES.add(Pattern.compile("/api/*"));
NO_AUTH_ROUTES.add("/api/user/authenticate");
NO_AUTH_ROUTES.add("/api/user/register");
}
private Logger LOG = LoggerFactory.getLogger(JWTTokenAuthFilter.class);
#Autowired
private UserService userService;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String authorizationHeader = request.getHeader("authorization");
String authenticationHeader = request.getHeader("authentication");
String route = request.getRequestURI();
// no auth route matching
boolean needsAuthentication = false;
for (Pattern p : AUTH_ROUTES) {
if (p.matcher(route).matches()) {
needsAuthentication = true;
break;
}
}
if(route.startsWith("/api/")) {
needsAuthentication = true;
}
if (NO_AUTH_ROUTES.contains(route)) {
needsAuthentication = false;
}
// Checking whether the current route needs to be authenticated
if (needsAuthentication) {
// Check for authorization header presence
String authHeader = null;
if (authorizationHeader == null || authorizationHeader.equalsIgnoreCase("")) {
if (authenticationHeader == null || authenticationHeader.equalsIgnoreCase("")) {
authHeader = null;
} else {
authHeader = authenticationHeader;
LOG.info("authentication: " + authenticationHeader);
}
} else {
authHeader = authorizationHeader;
LOG.info("authorization: " + authorizationHeader);
}
if (StringUtils.isBlank(authHeader) || !authHeader.startsWith("Bearer ")) {
throw new AuthCredentialsMissingException("Missing or invalid Authorization header.");
}
final String token = authHeader.substring(7); // The part after "Bearer "
try {
final Claims claims = Jwts.parser().setSigningKey(JWT_KEY)
.parseClaimsJws(token).getBody();
request.setAttribute("claims", claims);
// Now since the authentication process if finished
// move the request forward
filterChain.doFilter(request, response);
} catch (final Exception e) {
throw new AuthenticationFailedException("Invalid token. Cause:"+e.getMessage());
}
} else {
filterChain.doFilter(request, response);
}
}
}
Method that creates the Authentication token:
String token = Jwts.builder().setSubject(user.getEmail())
.claim("role", user.getRole().name()).setIssuedAt(new Date())
.signWith(SignatureAlgorithm.HS256, JWTTokenAuthFilter.JWT_KEY).compact();
authResp.put("token", token);
authResp.put("user", user);
Above i have all the claims that i am using on the JWT , i would like to request that the token is revoked after x ammount of time(of inactivity if possible).
How could i achieve this using JWT / Spring MVC / Angular JS / Spring Security
Set expiration for token
String token = Jwts.builder()
.setSubject(user.getEmail())
.claim("role", user.getRole().name())
.setIssuedAt(new Date())
.setExpiration(expirationDate)
.signWith(SignatureAlgorithm.HS256, WTTokenAuthFilter.JWT_KEY)
.compact();
Then, parseClaimsJws will raise ExpiredJwtException if currentTime>expirationDate
To revoke a valid token is a hard technique with no easy solutions:
1) Maintain a blacklist in server and compare for each request
2) Set a small expiration time and issue new token
3) Insert the login time in the token and compare if acomplish your criteria
4) remove the jwt in client side
Ser Invalidating client side JWT session