How to use c8yClient code in the Angular 6 App (typescript file) - cumulocity

For Example:
import { Client } from '#c8y/client';
const baseUrl = 'https://demos.cumulocity.com/';
const tenant = 'demos';
const user = 'user';
const password = 'pw';
(async () => {
const client = await Client.authenticate({
tenant,
user,
password
}), baseUrl);
const { data, paging } = await client.inventory.list();
// data = first page of inventory
const nextPage = await paging.next();
// nextPage.data = second page of inventory
})();
Consider that I have login module in an angular 6 application. How to use this above code and authenticate the user in the login.component.ts file?

Cumulocity has released a demo on Stackblitz how to log in the user. Basically you build a ngForm with username, password and tenant and pass that to the Cumulocity client:
async login() {
const client = new Client(new BasicAuth(
{
user: this.model.user,
password: this.model.password,
tenant: this.model.tenant
}),
`https://${this.model.tenant}.cumulocity.com`
);
try {
let user = await client.user.current();
this.cumulocity.client = client;
} catch (ex) {
this.cumulocity.client = null;
this.error.shown = true;
this.error.msg = ex.message;
}
}
In this case this.model is the data coming from an ngFrom and on button click the login()? function is executed. The this.cumulocity variable contains a service so that you can share the logged in client with other components.
Note: If you run this on a different server (not hosted), then you need to enable CORS in the Cumulocity administration.

Related

Google email offline access using react native expo app

I am creating one app using react native expo, which allow end user to login by their google account , and then applicaton try to save the access_token so that server based applicatin can use this to send the email on their behalf ,
But when using google sing in , i am not getting refresh token and not able to send the email ,
Here is code example which i am using
I tried below method to get the access request
const [request, response, promptAsync] = Google.useIdTokenAuthRequest({
clientId: "XXXXXXX",
androidClientId:"XXXXXXX",
iosClientId:"XXXXXXX"
});
const [initializing, setInitializing] = useState(true);
const [user, setUser] = useState();
const sendNotification=useNotification()
//console.log(sendNotification)
useEffect(() => {
if (response?.type === "success") {
const { id_token } = response.params;
const auth = getAuth();
const credential = GoogleAuthProvider.credential(id_token);
signInWithCredential(auth, credential);
let decoded = jwt_decode(id_token);
socialLogin(decoded)
}
}, [response]);
And on server using this code to sending email
const { google } = require('googleapis');
const path = require('path');
const fs = require('fs');
const credentials = require('./credentials.json');
// Replace with the code you received from Google
const code = 'XXXXXXX';
//const code="XXXXXXX"
const { client_secret, client_id, redirect_uris } = credentials.installed;
const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirect_uris[0]);
oAuth2Client.getToken(code).then(({ tokens }) => {
console.log('first')
const tokenPath = path.join(__dirname, 'token.json');
fs.writeFileSync(tokenPath, JSON.stringify(tokens));
console.log('Access token and refresh token stored to token.json');
}).catch(err=>console.log(err));
async function signInWithGoogleAsync() {
try {
const result = await Google.logInAsync({
androidClientId: YOUR_CLIENT_ID_HERE,
scopes: ["profile", "email"],
});
if (result.type === "success") {
onSignIn(result);
return result.accessToken;
} else {
return { cancelled: true };
}
} catch (e) {
return { error: true };
}
}
Well, I tried to create an application with Google login. To use the Google Sign-In method in a React Native Expo app, you will need to perform the following steps:
Set up a project in the Google Cloud Console and obtain a configuration file for your app.
Install the expo-google-sign-in package in your React Native app.
Import the GoogleSignIn object from the expo-google-sign-in package and use the initAsync method to initialize the Google Sign-In process.
Use the GoogleSignIn.askForPlayServicesAsync method to check if the device has the required Google Play Services installed.
Use the GoogleSignIn.signInAsync method to prompt the user to sign in with their Google account.
Once the user has signed in, you can use the accessToken and refreshToken properties of the returned object to make authorized requests to the Google APIs.
The code lines for the above steps are:
import { GoogleSignIn } from 'expo-google-sign-in';
// Initialize the Google Sign-In process
await GoogleSignIn.initAsync({
// Your config. values
});
// Check if the device has the required Google Play Services installed
const isPlayServicesAvailable = await GoogleSignIn.askForPlayServicesAsync();
if (!isPlayServicesAvailable) {
console.error('Google Play services are not available on this device.');
return;
}
// Prompt the user to sign in with their Google account
const { accessToken, refreshToken } = await GoogleSignIn.signInAsync();

Firebase updatePassword removes sign_in_second_factor phone property from token

I do a reautenticate with a user whom is already logged in as a Multifactor user
async reauthenticate(oldPassword: string): Promise<SignInEmailPassword> {
const user = firebase.auth().currentUser;
try {
if (user?.email) {
const cred = firebase.auth.EmailAuthProvider.credential(user.email, oldPassword);
await user.reauthenticateWithCredential(cred);
}
return { exception: '', token: '' };
} catch (reason) {
let phoneNumber = '****';
if ((reason as any).code === 'auth/multi-factor-auth-required') {
// The user is enrolled in MFA, must be verified
this.mfaResolver = (reason as any).resolver;
phoneNumber = (reason as any).resolver.hints[0].phoneNumber;
}
return { exception: 'auth/multi-factor-auth-required', phoneNumber };
}
}
I do the phone verification like
const phoneAuthProvider = new firebase.auth.PhoneAuthProvider();
const phoneOpts = {
multiFactorHint: this.mfaResolver.hints[0],
session: this.mfaResolver.session,
};
try {
this.verificationId = await phoneAuthProvider.verifyPhoneNumber(phoneOpts, recaptcha);
All good so far ( the recaptcha works with some other code, not mentioned here )
Then the actual SMS is verified, like:
const cred = firebase.auth.PhoneAuthProvider.credential(this.verificationId, phoneCode);
const multiFactorAssertion = firebase.auth.PhoneMultiFactorGenerator.assertion(cred);
const user = firebase.auth().currentUser;
try {
if (this.mfaResolver) {
await this.mfaResolver.resolveSignIn(multiFactorAssertion);
}
all good, and then finally
I can update the password with
const user = firebase.app().auth().currentUser;
if (user) {
await user.updatePassword(password);
}
If I console.log the token JUST before the updatePassword, I get my old token, with the
"sign_in_second_factor": "phone" property, but the token AFTER the updatePassword suddenly is without the sign_in_second_factor property, so basically it broke the token.
My solution is now to log out, and force the user to log back in ( again with MFA ), but an unnecessary step.
Is this avoidable,
to me it looks like a firebase bug, as it generates a valid token, WITHOUT a sign_in_second_factor being present, while it is a MFA firebase user.

cognito custom auth (CUSTOM_CHALLENGE) ignore retry because session expiration

my goal is to implement otp by sending a sms to user mobile. im able to achieve this using cognito custom auth flow, but, only works if the user success in the firts attemp, if the user enter a bad code, the session is gonna expire and a new code is required to be sent again, bad ux. i do need at least 3 attemps, which in theory are 3 sessions across this cognito auth flow.
im gonna share the four cognito lambdas (cognito triggers) i used for this: preSignUp, defineAuthChallenge, createAuthChallenge and verifyChanllenge
// preSignUp lambda
exports.handler = async (event) => {
event.response.autoConfirmUser = true;
event.response.autoVerifyPhone = true;
return event;
};
// defineAuthChallenge
exports.handler = async (event, context, callback) => {
if (event.request.session.length >= 3 && event.request.session.slice(-1)[0].challengeResult === false) {
// wrong OTP even After 3 sessions? FINISH auth, dont send token
event.response.issueToken = false;
event.response.failAuthentication = true;
} else if (event.request.session.length > 0 && event.request.session.slice(-1)[0].challengeResult === true) {
// Last answer was Correct! send token and FINISH auth
event.response.issueTokens = true;
event.response.failAuthentication = false;
} else {
// INIT flow - OR - not yet received correct OTP
event.response.issueTokens = false;
event.response.failAuthentication = false;
event.response.challengeName = 'CUSTOM_CHALLENGE';
}
return event;
};
// createAuthChallenge
exports.handler = async (event, context) => {
if (!event.request.session || event.request.session.length === 0) {
// create only once the otp, send over sms only once
var otp = generateOtp();
const phone = event.request.userAttributes.phone_number;
sendSMS(phone, otp);
} else {
// get previous challenge answer
const previousChallenge = event.request.session.slice(-1)[0];
otp = previousChallenge.challengeMetadata;
}
event.response = {
...event.response,
privateChallengeParameters: {
answer: otp
},
challengeMetadata: otp // save it here to use across sessions
};
return event
}
// verifyChanllenge
exports.handler = async (event, context) => {
event.response.answerCorrect = event.request.privateChallengeParameters.answer === event.request.challengeAnswer;
return event
}
for the client, which is a RN app, im using amplify, this is the flow in the app:
// SignIn form screen
import { Auth } from "aws-amplify";
const signUp = (phone) => {
Auth.signUp({
username: phone,
/** dummy pass since its required but unused for OTP */
password: "12345678"
}).then(() => {
// after signup, go an automatically login (which trigger sms to be sent)
otpSignIn(phone);
}).catch(({code}) => {
// signup fail because user already exists, ok, just try login it
if (code === SignUpErrCode.USER_EXISTS) {
otpSignIn(phone)
} else {
...
}
})
}
const otpSignIn = async (phoneNumber) => {
const cognitoUser = await Auth.signIn(phoneNumber)
setCognitoUser(cognitoUser);
navigate("ConfirmNumber", {phoneNumber});
}
import { Auth } from "aws-amplify";
let cognitoUser;
export function setCognitoUser(user) {
console.log('setCognitoUser', user)
cognitoUser = user;
}
export function sendChallenge(challengeResponse) {
return Auth.sendCustomChallengeAnswer(cognitoUser, challengeResponse)
}
// Confirm number screen
const onChangeText = (value) => {
if (value.length === 4) {
try {
const user = await sendChallenge(value)
// WEIRD THING NUMBER 1
// when the user send the second attempt, no error is raised, this promise is resolve!
// even when the trigger *verifyChanllenge* is returning false.
} catch (err) {
// WEIRD THING NUMBER 2
// from the trigger *createAuthChallenge* if i define the anser in the if block,
// and not store such answer for future use (i do that in else block), then,
// for the second..third attempt the error raised here is that *Invalid session for user* which mean session has expired,
// what i need is to persist session until third attempt
}
}
}
// this is amplify config: try 1
const awsExports = {
Auth: {
region: ...,
userPoolId: ...,
userPoolWebClientId: ...,
authenticationFlowType: 'CUSTOM_AUTH',
},
...
}
Amplify.configure(awsExports);
// this is amplify config: try 2
import {Auth} from "aws-amplify"
Auth.configure({
authenticationFlowType: 'CUSTOM_AUTH'
});
everything is correct in the code above, and the config for amplify authenticationFlowType: 'CUSTOM_AUTH' is not necessary.
the problem is that Auth.sendCustomChallengeAnswer(cognitoUser, challengeResponse) is not raising an error when the trigger defineAuthChallenge set this combination:
event.response.issueTokens = false;
event.response.failAuthentication = false;
event.response.challengeName = 'CUSTOM_CHALLENGE';
which presents the next attempt.
so i found a way to check the error when the user fail the otp:
const sendCode = async (value) => {
try {
// send the answer to the User Pool
// this will throw an error if it's the 3rd wrong answer
const user = await sendChallenge(value);
// the answer was sent successfully, but it doesnt mean it is the right one
// so we should test if the user is authenticated now
// this will throw an error if the user is not yet authenticated:
await Auth.currentSession();
} catch (err) {
setError(true);
}
}

Nextjs Auth0 get data in getServerSideProps

Im using Auth0 to authenticate users.
Im protected api routes like this:
// pages/api/secret.js
import { withApiAuthRequired, getSession } from '#auth0/nextjs-auth0';
export default withApiAuthRequired(function ProtectedRoute(req, res) {
const session = getSession(req, res);
const data = { test: 'test' };
res.json({ data });
});
My problem is when I'm trying to fetch the data from getServerSideProps I'm getting 401 error code.
If I use useEffect Im able to get data from api route.
Im trying to fetch the data like this:
export const getServerSideProps = withPageAuthRequired({
async getServerSideProps(ctx) {
const res = await fetch('http://localhost:3000/api/secret');
const data = await res.json();
return { props: { data } };
},
});
Im getting the following response:
error: "not_authenticated", description: "The user does not have an active session or is not authenticated"
Any idea guys? Thanks!!
When you call from getServerSideProps the protected API end-point you are not passing any user's context (such as Cookies) to the request, therefore, you are not authenticated.
When you call from useEffect it runs inside your browser, which attaches all cookies to the request, one of them is the session cookie.
You need to forward the session cookie that was passed to the getServerSideProps (by the browser) to the API call.
export const getServerSideProps = withPageAuthRequired({
async getServerSideProps(ctx) {
const res = await fetch('http://localhost:3000/api/secret', {
headers: { Cookie: ctx.req.headers.cookie },
// ---------------------------^ this req is the browser request to the getServersideProps
});
const data = await res.json();
return { props: { data } };
},
});
For more info.
#auth0/nextjs-auth0 has useUser hook. This example is from: https://auth0.com/blog/ultimate-guide-nextjs-authentication-auth0/
// pages/index.js
import { useUser } from '#auth0/nextjs-auth0';
export default () => {
const { user, error, isLoading } = useUser();
if (isLoading) return <div>Loading...</div>;
if (error) return <div>{error.message}</div>;
if (user) {
return (
<div>
Welcome {user.name}! Logout
</div>
);
}
// if not user
return Login;
};
Note that authentication takes place on the server in this model,
meaning that the client isn't aware that the user is logged in. The
useUser hook makes it aware by accessing that information in the
initial state or through the /api/auth/profile endpoint, but it won't
expose any id_token or access_token to the client. That information
remains on the server side.
Custom HOF:
// getData is a callback function
export const withAuth = (getData) => async ({req, res}) => {
const session = await auth0.getSession(req);
if (!session || !session.user) {
res.writeHead(302, {
Location: '/api/v1/login'
});
res.end();
return {props: {}};
}
const data = getData ? await getData({req, res}, session.user) : {};
return {props: {user: session.user, ...data}}
}
Example of using:
export const getServerSideProps = withAuth(async ({req, res}, user) => {
const title = await getTitle();
return title;
});

How to persist CognitoUser during signIn with CUSTOM_AUTH authentication flow

My React Native app uses Amplify for a CUSTOM_AUTH authentication flow. The user receives a link via email to satisfy a challengeAnswer request. The process is like this:
User initiatiates sign in:
const cognitoUser = await Auth.signIn(username);
Email is sent to user via lambda.
User leaves app to retrieve email.
User clicks a link in the email which routes user back to the app via the RN Linking api.
The code from the link is processed with:
await Auth.sendCustomChallengeAnswer(
cognitoUser,
authChallengeAnswer
);
Usually this works well, but there is no guarantee that the cognitoUser object will exist after the app has been backgrounded while the user retrieves the email. There is a non-zero chance that iOS could dump the app during this time, and the cognitoUser var would be gone forcing the user to restart the sign in process. I'm looking for a way to persist the cognitoUser object somehow so if iOS decides the app needs to die this var can be retrieved from cache.
I'm able to cache the object into the Amplify cache (AsyncStorage) with
await Cache.setItem("cognitoUser", cognitoUser);
then fetch with
await Cache.getItem("cognitoUser");
which fails with
TypeError: user.sendCustomChallengeAnswer is not a function
because the process of caching it lost all its __proto__ functions. Its just retrieved as a basic object.
I suspect the cause is that I'm not using TypeScript, and the object loses some type information somehow.
Is there a better way of persisting this CognitoUser object so I can guarantee it exists after the user leaves/returns to the app as is needed in a CUSTOM_AUTH flow.
I use the following code to persist CognitoUser during sign in with CUSTOM_AUTH authentication flow:
import Auth from '#aws-amplify/auth'
import { CognitoUser } from 'amazon-cognito-identity-js'
const CUSTOM_AUTH_TTL = 5 * 60 * 1000 // Milliseconds
interface CustomAuthSession {
username: string
session: string
// Milliseconds after epoch
expiresAt: number
}
function clearCustomAuthSession() {
window.localStorage.removeItem('CustomAuthSession')
}
function loadCustomAuthSession(): CognitoUser {
const raw = window.localStorage.getItem('CustomAuthSession')
if (!raw) {
throw new Error('No custom auth session')
}
const storedSession: CustomAuthSession = window.JSON.parse(raw)
if (storedSession.expiresAt < window.Date.now()) {
clearCustomAuthSession()
throw new Error('Stored custom auth session has expired')
}
const username = storedSession.username
// Accessing private method of Auth here which is BAD, but it's still the
// safest way to restore the custom auth session from local storage, as there
// is no interface that lets us do it.
// (If we created a new user pool object here instead to pass to a
// CognitoUser constructor that would likely result in hard to catch bugs,
// as Auth can assume that all CognitoUsers passed to it come from its pool
// object.)
const user: CognitoUser = (Auth as any).createCognitoUser(username)
// Session is not exposed to TypeScript, but it's a public member in the
// JS code.
;(user as any).Session = storedSession.session
return user
}
function storeCustomAuthSession(cognitoUser: CognitoUser) {
// Session isn't exposed to TypeScript, but it's a public member in JS
const session = (cognitoUser as any).Session
const expiresAt = window.Date.now() + CUSTOM_AUTH_TTL
const otpSession: CustomAuthSession = {
session,
expiresAt,
username: cognitoUser.getUsername(),
}
const json = window.JSON.stringify(otpSession)
window.localStorage.setItem('CustomAuthSession', json)
}
You can reconstruct the CognitoUser object manually from your serialized object in localStorage or cache:
import { CognitoUser, CognitoUserPool } from 'amazon-cognito-identity-js';
const pool = new CognitoUserPool({
UserPoolId: cognitoObject.pool.userPoolId,
ClientId: cognitoObject.pool.clientId,
endpoint: cognitoObject.client.endpoint,
Storage: window.localStorage,
AdvancedSecurityDataCollectionFlag: cognitoObject.advancedSecurityDataCollectionFlag,
})
const cognitoUser = new CognitoUser({
Username: cognitoObject.username,
Pool: pool,
Storage: window.localStorage,
})
cognitoUser.Session = cognitoObject.Session
await Auth.completeNewPassword(cognitoUser, newPassword, cognitoObject.challengeParams)
Had to add the import statement myself, but got the general idea here: https://github.com/aws-amplify/amplify-js/issues/1715#issuecomment-800999983
I had this same issue and the simplest solution was to store it as a global variable within the slice.
authSlice.ts:
// we use this to temporarily store CognitoUser for MFA login.
// CognitoUser is not serializable so we cannot store it on Redux.
let cognitoUser = {};
export const doLogin = createAsyncThunk(
"auth/login",
async ({ email, password }: UserCredentials): Promise<Login | MFA> => {
const res = await Auth.signIn(email, password);
if (res.challengeName === "SOFTWARE_TOKEN_MFA") {
// we use this to temporarily store CognitoUser for MFA login.
// CognitoUser is not serializable so we cannot store it on Redux.
cognitoUser = res;
return {
status: "MFA",
user: null,
};
} else {
const user = await getUser();
return { user, status: "OK" };
}
}
);
export const confirmMFA = createAsyncThunk("auth/confirmMFA", async ({ mfa }: UserMFA) => {
if (!cognitoUser) {
throw new Error("Invalid flow?!");
}
await Auth.confirmSignIn(cognitoUser, mfa, "SOFTWARE_TOKEN_MFA");
const user = await getUser();
return { user, status: "OK" };
});
const getUser = async (): Promise<User> => {
const session = await Auth.currentSession();
// #ts-ignore https://github.com/aws-amplify/amplify-js/issues/4927
const { accessToken } = session;
if (!accessToken) {
throw new Error("Missing access token");
}
setCredentials(accessToken.jwtToken);
const user = await Auth.currentAuthenticatedUser();
return user.attributes;
};
Our requirement was also the same and we managed to get the customAuth flow working by creating cognitoUserPool and cognitoUser instance from localStorage/sessionStorage before calling sendCustomChallengeAnswer.
Example:
const userPoolData = {
Attributes: values(from localStorage);
}
const cognitoUserPool = new CognitoUserPool(userPoolData);
const userData = {
Attributes: values(from localStorage);
}
const cognitoUser = new CognitoUser(userData);
Auth.sendCustomChallengeAnswer(cognitoUser, validationCode);