Azure b2c - Msal js - SPA - Edit profile - azure-active-directory

Did anyone try 'profile edit' with MSAL with spa? If so can you please point me to some demo samples? Thanks in advance.
I was able to integrate 'SignIn', 'SignUp' and 'PasswordReset' but when integrating profile edit it did the route to 'profile edit' policy but the issue is, the application is asking to sign in again to edit the user profile.
Below is the code,
private clientApplication: Msal.UserAgentApplication;
constructor(private router: Router, private token: tokenDataService) {
this.clientApplication =
new Msal.UserAgentApplication(
environment.clientID,
this.authority,
this.authCallback.bind(this),
{
redirectUri: window.location.origin
});
}
public login(): void {
this.clientApplication.loginRedirect(environment.b2cScopes);
}
public editProfile() {
let clientApp = window.msal as Msal.UserAgentApplication;
clientApp.authority = this.profileEditAuthority;
clientApp.loginRedirect(environment.b2cScopes);
}
public logout(): void {
this.clientApplication.logout();
}
public firstLogin(): string {
return this.clientApplication.getUser().idToken['newUser'];
}
public isOnline(): boolean {
return this.clientApplication.getUser() != null;
}
public getUser(): User {
return this.clientApplication.getUser();
}
public getAuthenticationToken(): Promise<string> {
return this.clientApplication.acquireTokenSilent(environment.b2cScopes)
.then(token => {
this.token.set(token);
console.log('hi')
console.log(token);
return token;
}).catch(error => {
return this.clientApplication.acquireTokenPopup(environment.b2cScopes)
.then(token => {
return Promise.resolve(token);
}).catch(innererror => {
console.error('Could not retrieve token from popup.', innererror);
return Promise.resolve('');
});
});
}
private authCallback(errorDesc: any, token: any, error: any, tokenType: any) {
this.router.navigate(['/pages/home']);
if (error) {
if (errorDesc.indexOf("AADB2C90118") > -1) {
let clientApp = window.msal as Msal.UserAgentApplication;
clientApp.authority = this.resetpasswordAuthority;
clientApp.loginRedirect(environment.b2cScopes);
}
console.error(`${error} ${errorDesc}`);
}
}

Related

How to implement Authorization with Custom Directives in apollo with graphql-tools/utils?

I know that Apollo 2 allowed custom directives by extending the class "SchemaDirectiveVisitor." However, I am using apollo 3 and I know that the way to achieve this now is by using graphql-tools/utils and graphql-tools/schema.
In my index.js I have the following code:
const serverServer = async () => {
app.use(AuthMiddleware);
app.use(
cors({
origin: 'mydomain',
})
);
let schema = makeExecutableSchema({
typeDefs: [typeDefsLibrary, typeDefsDynamicContent, userTypeDefs],
resolvers: {
Query,
Mutation,
Article,
Blog,
Podcast,
SermonNotes,
Sermon,
// dynamic Content
Friday,
Thursday,
// Post Content
Commentary,
Quote,
Thought,
UserContent_SermonNotes,
// User Content
User,
All_Posts,
},
});
schema = AuthorizationDirective(schema, 'auth');
const apolloServer = new ApolloServer({
schema,
context: ({ req }) => {
const { isAuth, user } = req;
return {
req,
isAuth,
user,
};
},
});
await apolloServer.start();
apolloServer.applyMiddleware({ app: app, path: '/api' });
app.listen(process.env.PORT, () => {
console.log(`listening on port 4000`);
});
};
serverServer();
then on my schema file I have:
directive #auth(requires: [RoleName] ) on OBJECT | FIELD_DEFINITION
enum RoleName {
SUPERADMIN
ADMIN
}
type Commentary #auth(requires: [SUPERADMIN, ADMIN]) {
ID: ID
USER_ID: ID
VERSE_ID: String
body: String
category_tags: String
referenced_verses: String
verse_citation: String
created_date: String
posted_on: String
creator(avatarOnly: Boolean): User
comments(showComment: Boolean): [Commentary_Comment]
approvals: [Commentary_Approval]
total_count: Int
}
and this is my custom directive code:
const { mapSchema, getDirective, MapperKind } = require('#graphql-tools/utils');
const { defaultFieldResolver } = require('graphql');
const { ApolloError } = require('apollo-server-express');
//const { logging } = require('../../helpers');
module.exports.AuthorizationDirective = (schema, directiveName) => {
return mapSchema(schema, {
[MapperKind.FIELD]: (fieldConfig, _fieldName, typeName) => {
const authDirective = getDirective(schema, fieldConfig, directiveName);
console.log('auth Directive line 10: ', authDirective);
if (authDirective && authDirective.length) {
const requiredRoles = authDirective[0].requires;
if (requiredRoles && requiredRoles.length) {
const { resolve = defaultFieldResolver } = fieldConfig;
fieldConfig.resolve = function (source, args, context, info) {
if (requiredRoles.includes('PUBLIC')) {
console.log(
`==> ${context.code || 'ANONYMOUS'} ACCESSING PUBLIC RESOLVER: ${
info.fieldName
}`
);
//logging(context, info.fieldName, args);
return resolve(source, args, context, info);
}
if (!requiredRoles.includes(context.code)) {
throw new ApolloError('NOT AUTHORIZED', 'NO_AUTH');
}
console.log(`==> ${context.code} ACCESSING PRIVATE RESOLVER: ${info.fieldName}`);
//logging(context, info.fieldName, args);
return resolve(source, args, context, info);
};
return fieldConfig;
}
}
},
});
};
But is not working. It seems like it is not even calling the Custom Directive. As you see I have a "console.log('auth Directive line 10: ', authDirective);" on my schema directive function that return "undefined."
I know this post is so ling but I hope someone can help!
Thanks in advance!
Below is the code worked for me
I have used [MapperKind.OBJECT_FIELD]: not [MapperKind.FIELD]:
I have referred this from #graphql-tools ->
https://www.graphql-tools.com/docs/schema-directives#enforcing-access-permissions
`
const { mapSchema, getDirective, MapperKind } = require('#graphql-tools/utils');
const { defaultFieldResolver } = require('graphql');
const HasRoleDirective = (schema, directiveName) => {
return mapSchema(schema, {
// Executes once for each object field in the schems
[MapperKind.OBJECT_FIELD]: (fieldConfig, _fieldName, typeName) => {
// Check whether this field has the specified directive
const authDirective = getDirective(schema, fieldConfig, directiveName);
if (authDirective && authDirective.length) {
const requiredRoles = authDirective[0].requires;
// console.log("requiredRoles: ", requiredRoles);
if (requiredRoles && requiredRoles.length) {
// Get this field's original resolver
const { resolve = defaultFieldResolver } = fieldConfig;
// Replace the original resolver with function that "first" calls
fieldConfig.resolve = function (source, args, context, info) {
// console.log("Context Directive: ", context);
const { currentUser } = context;
if(!currentUser) throw new Error("Not Authenticated");
const { type } = currentUser['userInfo']
const isAuthorized = hasRole(type, requiredRoles);
if(!isAuthorized) throw new Error("You Have Not Enough Permissions!")
//logging(context, info.fieldName, args);
return resolve(source, args, context, info);
};
return fieldConfig;
}
}
}
})
}
`

Why is my object type not getting updated?

I'm creating a permission service using react typescript and I ran into the following problem. I have the class:
import {IPermission} from "../interfaces/IPermission";
class PermissionService {
private permissions: IPermission[] = [];
constructor(permissions: IPermission[]) {
this.permissions = permissions;
}
public getValue(key: string): IPermission['value'] {
const perm = this.permissions.find(permission => permission.key === key);
if (!perm) {
throw new Error('Could not find the permission');
}
return perm.value;
}
public modifyPermission(key: string, defaultValue: any, value: any): void {
const perms = [...this.permissions];
for (let i = 0; i < perms.length; i++) {
perms[i].defaultValue = defaultValue;
perms[i].value = value
}
this.permissions = perms;
console.log(perms);
}
public parseActivePermissions(permissions: IPermission[]): IPermission[] {
this.permissions.forEach(permission => {
permissions.forEach(activePermission => {
if (permission.key === activePermission.key) {
permission.defaultValue = activePermission.defaultValue;
permission.value = activePermission.value;
}
})
})
return this.permissions;
}
public getAll(): IPermission[] {
return this.permissions;
}
}
export default PermissionService;
and an AdminPermissions data file
import PermissionService from "../services/permission.service";
import {IPermission} from "../interfaces/IPermission";
import Permissions from "./Permissions";
const service: PermissionService = new PermissionService(Permissions);
service.modifyPermission('canAccessAcp', true, true);
const AdminPermissions: IPermission[] = service.getAll();
export default AdminPermissions;
The problem is, the service.modifyPermission() does not update the defaultValue and value of the permission. It's still false when console logging. Why is that?
UPDATE #1
Changed the file a bit. Still doesn't work. Now I'm directly changing the values, but they still log as false.
class AdminPermissions {
public getAll(): IPermission[] {
const service: PermissionService = new PermissionService(Permissions);
service.permissions.forEach(permission => {
if (permission.key === 'canAccessAcp') {
permission.defaultValue = true;
permission.value = true;
}
})
return service.permissions;
}
}
The problem is that with forEach you are not changing the actual value of each items, so you should do something like this:
class AdminPermissions {
public getAll(): IPermission[] {
const service: PermissionService = new PermissionService(Permissions);
return service.permissions.map(permission => {
if (permission.key === 'canAccessAcp') {
return (
{
...permission,
defaultValue: true,
value: true
}
)
}
return permission
});
}
}
I found a solution.
In the permission.service.ts
public modifyPermission(key: string, defaultValue: any, value: any): void {
const perms: IPermission[] = this.permissions.map(permission => {
if (permission.key === key) {
console.log('found permission');
return {
...permission,
defaultValue,
value
}
}
return permission
})
this.permissions = perms;
}

Using SP.Modal dialog in SPFX command set

My requirement is to open a SharePoint page in a modal dialogue using command set in a list. I have followed this:
MSDN tutorial to create command set
and this question:
How to refernence sp.js
This is my .ts file code
import { override } from '#microsoft/decorators';
import { Log } from '#microsoft/sp-core-library';
import {
BaseListViewCommandSet,
Command,
IListViewCommandSetListViewUpdatedParameters,
IListViewCommandSetExecuteEventParameters
} from '#microsoft/sp-listview-extensibility';
import { Dialog } from '#microsoft/sp-dialog';
import { SPComponentLoader } from '#microsoft/sp-loader';
import * as strings from 'DocManagerCommandSetStrings';
require('sp-init');
require('microsoft-ajax');
require('sp-runtime');
require('sharepoint');
/**
* If your command set uses the ClientSideComponentProperties JSON input,
* it will be deserialized into the BaseExtension.properties object.
* You can define an interface to describe it.
*/
export interface IDocManagerCommandSetProperties {
// This is an example; replace with your own properties
sampleTextOne: string;
sampleTextTwo: string;
}
const LOG_SOURCE: string = 'DocManagerCommandSet';
export default class DocManagerCommandSet extends BaseListViewCommandSet<IDocManagerCommandSetProperties> {
#override
public onInit(): Promise<void> {
Log.info(LOG_SOURCE, 'Initialized DocManagerCommandSet');
return Promise.resolve();
}
#override
public onListViewUpdated(event: IListViewCommandSetListViewUpdatedParameters): void {
const compareOneCommand: Command = this.tryGetCommand('COMMAND_1');
if (compareOneCommand) {
// This command should be hidden unless exactly one row is selected.
compareOneCommand.visible = event.selectedRows.length === 1;
}
}
#override
public onExecute(event: IListViewCommandSetExecuteEventParameters): void {
switch (event.itemId) {
case 'COMMAND_1':
Dialog.alert(`${this.properties.sampleTextOne}`);
break;
case 'COMMAND_2':
//DocManagerCommandSet._loadSPJSOMScripts();
var options = {
title: "My Dialog Title",
width: 400,
height: 600,
url: "/_layouts/DialogPage.aspx" };
var value = SP.UI.ModalDialog.showModalDialog(options);
// Dialog.alert(`${this.properties.sampleTextTwo}`);
break;
default:
throw new Error('Unknown command');
}
}
private static getSiteCollectionUrl(): string {
let baseUrl = window.location.protocol + "//" + window.location.host;
const pathname = window.location.pathname;
const siteCollectionDetector = "/sites/";
if (pathname.indexOf(siteCollectionDetector) >= 0) {
baseUrl += pathname.substring(0, pathname.indexOf("/", siteCollectionDetector.length));
}
return baseUrl;
}
private static _loadSPJSOMScripts() {
const siteColUrl = "https://shelldevelopment.sharepoint.com/sites/SPODA0332/";
SPComponentLoader.loadScript(siteColUrl + '/_layouts/15/init.js', {
globalExportsName: '$_global_init'
})
.then((): Promise<{}> => {
return SPComponentLoader.loadScript(siteColUrl + '/_layouts/15/MicrosoftAjax.js', {
globalExportsName: 'Sys'
});
})
.then((): Promise<{}> => {
return SPComponentLoader.loadScript(siteColUrl + '/_layouts/15/SP.Runtime.js', {
globalExportsName: 'SP'
});
})
.then((): Promise<{}> => {
return SPComponentLoader.loadScript(siteColUrl + '/_layouts/15/SP.js', {
globalExportsName: 'SP'
});
}) .then((): Promise<{}> => {
return SPComponentLoader.loadScript('/_layouts/15/sp.init.js', {
globalExportsName: 'SP'
});
}).then((): Promise<{}> => {
return SPComponentLoader.loadScript('/_layouts/15/sp.ui.dialog.js', {
globalExportsName: 'SP'
});
});
}
}
I am getting the following error:
cannot find the name 'SP'.in the line
SP.UI.ModalDialog.showModalDialog(options)
Kindly provide some insights as I am a beginner in SPFX
Theoretically you need to uncomment //DocManagerCommandSet._loadSPJSOMScripts(); and wait for the promise to return.
Update the loadSPJSOMScripts message to return the promise:
private static _loadSPJSOMScripts(): Promise<void> {
const siteColUrl = "https://shelldevelopment.sharepoint.com/sites/SPODA0332/";
return SPComponentLoader.loadScript(siteColUrl + '/_layouts/15/init.js', {
globalExportsName: '$_global_init'
})
// [the rest of the calls... ]
.then(_ => {});
}
to load in the onInit():
public onInit(): Promise<void> {
return Promise.resolve()
.then(_ => {
return DocManagerCommandSet._loadSPJSOMScripts();
});
}
Or in your onExecute:
#override
public onExecute(event: IListViewCommandSetExecuteEventParameters): void {
let launchModal = false;
switch (event.itemId) {
case 'COMMAND_1':
Dialog.alert(`${this.properties.sampleTextOne}`);
break;
case 'COMMAND_2':
launchModal = true;
break;
// ...
}
if (launchModal) {
DocManagerCommandSet._loadSPJSOMScripts()
.then(_ => {
var options = {
title: "My Dialog Title",
width: 400,
height: 600,
url: "/_layouts/DialogPage.aspx"
};
var value = SP.UI.ModalDialog.showModalDialog(options);
});
}
}
That being said, there may be better ways to work with JSOM in SPFX.

Angular 2 Load data through server API : data.slice error

Im trying to load the data from my API to custom component using Angular2 ng Smart table plugin.
AS per their documentation (https://github.com/akveo/ng2-smart-table/blob/master/src/app/pages/examples/server/basic-example-load.component.ts)
i have my component like:
import { LocalDataSource } from 'ng2-smart-table';
import { ProductService } from '../../../services/product.service';
export class CategoryItemsComponent implements OnInit {
...
source: LocalDataSource;
constructor(private productService: ProductService,
private flashMessage: FlashMessagesService,
private router: Router,
http: Http) {
this.source = new LocalDataSource();
this.productService.getProductsOncategory(this.categoryid).subscribe((data) => {
this.source.load(data);
});
}
ProductService .ts
getProductsOncategory(category_id) {
let catUrl = "http://localhost:5000/products/getProductsOncategory"
let headers = new Headers();
headers.append('Content-Type', 'application/json');
let catIdObj = JSON.stringify({ category_id: category_id })
return this.http.post(catUrl, catIdObj, { headers: headers })
.map((response: Response) => response.json())
.do(data => console.log(JSON.stringify(data)))
.catch(this.handleError);
}
The above API used in the service function works perfect in my postman.
Now i need to load the dame data from that API into my custom component.
I am getting this error:
ERROR TypeError: this.data.slice is not a function
at LocalDataSource.webpackJsonp.../../../../ng2-smart-table/lib/data-source/local/local.data-source.js.LocalDataSource.getElements (http://localhost:4200/1.chunk.js:22280:30)
at LocalDataSource.webpackJsonp.../../../../ng2-smart-table/lib/data-source/data-source.js.DataSource.emitOnChanged (http://localhost:4200/1.chunk.js:22185:14)
at LocalDataSource.webpackJsonp.../../../../ng2-smart-table/lib/data-source/data-source.js.DataSource.load (http://localhost:4200/1.chunk.js:22105:14)
at LocalDataSource.webpackJsonp.../../../../ng2-smart-table/lib/data-source/local/local.data-source.js.LocalDataSource.load (http://localhost:4200/1.chunk.js:22243:38)
Ok i got it by using like:
source: LocalDataSource;
constructor(private productService: ProductService,
private flashMessage: FlashMessagesService,
private router: Router,
http: Http)
{
this.source = new LocalDataSource();
}
onChange(categoryid) {
this.productService.getProductsOncategory(categoryid).subscribe(data => {
if (data.success) {
this.source.load(data.products);
console.log('Products obtained');
} else {
console.log('Not obtained!');
}
});
}
Had the same problem. It solved when i check all match columns and add the missing in table data. For example i delared a variable
Settings = {
....
columns: {
id: {
title: 'id',
show: false,
type: 'string',
},
name: {
title: 'name',
type: 'string',
},
//compare columns in json response in same variable declaration
//for your data table [source]
}
}
And in second case your table try get data from dataTableSource before full data loading, to avoid this use setTimeout(); method and set more time.
For Example:
getChildData(): Promise<any> {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(this.childsList);
}, 4000);//<= increase this value
});
}
excuse me for my english))

Using hello.js with React.js

I'd like to understand how to make Hello.js work with React.js , especially the custom event handler hello.on
As I'm new to React.js, I don't understand how to bind non React events into the app flow.
I tried putting the event handler in the componentDidMount handler
handleClick(){
hello('twitter').login();
}
componentDidMount(){
hello.on('auth.login', function(auth) {
// Call user information, for the given network
hello(auth.network).api('/me').then(function(r) {
console.log(r);
});
});
hello.init({
'twitter' : 'J1jqqO50tcLtLx8Js0VDitjZW'
},
{
redirect_uri:'/',
oauth_proxy: 'https://auth-server.herokuapp.com/proxy'
});
}
thanks
And 3 years later:
You need a class for authentication, for example:
import * as React from "react";
import * as hello from "hellojs";
import { Event } from "../interfaces/Event";
export class Authentication extends React.Component<{}, { sendEvent: boolean }> {
constructor(public props, public context) {
super(props, context);
this.state = {
sendEvent: true
};
}
public login(network) {
hello.init({
aad: {
name: "Azure Active Directory",
oauth: {
version: 2,
auth: "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
grant: "https://login.microsoftonline.com/common/oauth2/v2.0/token"
},
// Authorization scopes
scope: {
// you can add as many scopes to the mapping as you want here
profile: "user.read",
offline_access: ""
},
scope_delim: " ",
login: p => {
if (p.qs.response_type === "code") {
// Let's set this to an offline access to return a refresh_token
p.qs.access_type = "offline_access";
}
},
base: "https://www.graph.microsoft.com/v1.0/",
get: {
me: "me"
},
xhr: p => {
if (p.method === "post" || p.method === "put") {
JSON.parse(p);
} else if (p.method === "patch") {
hello.utils.extend(p.query, p.data);
p.data = null;
}
return true;
},
// Don't even try submitting via form.
// This means no POST operations in <=IE9
form: false
}
});
hello.init(
{
aad: "ClientID"
},
{
redirect_uri: "YOUR REDIRECT_URI",
//redirect_uri: 'https://localhost:4321/temp/workbench.html',
scope: "user.read"
}
);
// By defining response type to code, the OAuth flow that will return a refresh token to be used to refresh the access token
// However this will require the oauth_proxy server
hello(network)
.login({ display: "none" })
.then(
authInfo => {
console.log(authInfo);
localStorage.setItem("logged", authInfo.authResponse.access_token);
},
e => {
console.error("Signin error: " + e.error.message);
}
);
}
//when the component is mounted you check the localstorage
//logged ==> undefined you call login and save a token in localstorage
//logged ==> with a token -> setEvent call a function that use graph api
public componentDidMount() {
let logged = localStorage["logged"];
if (logged === undefined) this.login("aad");
else {
if (this.state.sendEvent) {
this.props.setEvent(null);
this.props.setEvent(Event.GET_ALL_USERS);
}
}
}
public render() {
return null;
}
}
the file name is auth.tsx and you can call this class in the main react class:
export class mainClass extends React.Component{
......
......
private getEvent = (event) => {
this.setState({ event: event });
//HERE YOU recive the event when auth is ready
}
public render(){
<Authentication setEvent={this.getEvent} />
}
}

Resources