Skip to content

Commit b3ab1d4

Browse files
committed
Added support for authenticated directive, logout token deletion
1 parent 6f1c4cd commit b3ab1d4

File tree

3 files changed

+90
-5
lines changed

3 files changed

+90
-5
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
},
6161
"dependencies": {
6262
"apollo-link": "^1.2.2",
63+
"apollo-utilities": "^1.0.26",
6364
"graphql-tag": "^2.10.0"
6465
},
6566
"peerDependencies": {}

src/index.ts

Lines changed: 83 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import {ApolloLink, createOperation, FetchResult, NextLink, Observable, Operation} from 'apollo-link';
2+
import {removeDirectivesFromDocument} from 'apollo-utilities';
3+
import { DirectiveNode, FieldNode, OperationDefinitionNode } from 'graphql';
24
import gql from 'graphql-tag';
35
import MemoryStorage from './storage/MemoryStorage';
46
import {IAuthTokenSet, ISlicknodeLinkOptions, IStorage} from './types';
@@ -29,6 +31,11 @@ export const LOGOUT_MUTATION = gql`mutation logout($refreshToken: String) {
2931
}
3032
}`;
3133

34+
const authenticationDirectiveRemoveConfig = {
35+
test: (directive: DirectiveNode) => directive.name.value === 'authenticate',
36+
remove: false,
37+
};
38+
3239
/**
3340
* SlicknodeLink instance to be used to load data with apollo-client
3441
* from slicknode GraphQL servers
@@ -74,15 +81,86 @@ export default class SlicknodeLink extends ApolloLink {
7481
...authHeaders,
7582
},
7683
}));
77-
forward(operation).subscribe(observer);
84+
85+
const definitions = operation.query.definitions;
86+
// Find current operation in definitions
87+
const currentOperation: OperationDefinitionNode | null = definitions.find((operationDefinition) => {
88+
return (
89+
operationDefinition.kind === 'OperationDefinition' &&
90+
operationDefinition.name &&
91+
operationDefinition.name.value === operation.operationName
92+
);
93+
}) as OperationDefinitionNode | null;
94+
95+
// Check mutations for directives and logoutMutation
96+
const resultListeners: Array<(value: any) => void> = [];
97+
if (currentOperation.operation === 'mutation') {
98+
const fields: FieldNode[] = [];
99+
currentOperation.selectionSet.selections.forEach((selectionNode) => {
100+
if (selectionNode.kind === 'Field') {
101+
fields.push(selectionNode);
102+
} else {
103+
// @TODO: Collect all relevant fields recursively from fragments / child fragments
104+
// tslint:disable-next-line no-console
105+
console.warn('Fragments not supported on Mutation type for slicknode-apollo-link');
106+
}
107+
});
108+
109+
fields.forEach((field) => {
110+
if (field.name.value === 'logoutUser') {
111+
// Subscribe to result to remove auth tokens from storage
112+
resultListeners.push(() => {
113+
this.debug('Removing auth tokens from storage');
114+
this.logout();
115+
});
116+
} else if (
117+
field.directives &&
118+
field.directives.find((directive) => directive.name.value === 'authenticate')
119+
) {
120+
const fieldName = field.alias ? field.alias.value : field.name.value;
121+
// Subscribe to result to set auth token set
122+
resultListeners.push((result) => {
123+
// Validate auth token set and update tokens if valid
124+
if (
125+
result.data &&
126+
result.data.hasOwnProperty(fieldName) &&
127+
typeof result.data[fieldName] === 'object'
128+
) {
129+
const tokenSet = result.data[fieldName];
130+
if (
131+
typeof tokenSet.accessToken === 'string' &&
132+
typeof tokenSet.accessTokenLifetime === 'number' &&
133+
typeof tokenSet.refreshToken === 'string' &&
134+
typeof tokenSet.refreshTokenLifetime === 'number'
135+
) {
136+
// Update auth tokens in storage of link
137+
this.setAuthTokenSet(tokenSet);
138+
this.debug('Login successful, auth token set updated');
139+
} else {
140+
this.debug('The auth token set has no valid format');
141+
}
142+
} else {
143+
this.debug('No valid token set returned');
144+
}
145+
});
146+
}
147+
});
148+
}
149+
// Remove @authenticated directives from document
150+
operation.query = removeDirectivesFromDocument(
151+
[ authenticationDirectiveRemoveConfig ],
152+
operation.query,
153+
);
154+
const nextObservable = forward(operation);
155+
156+
// Add result listeners for token and logout processing
157+
resultListeners.map((listener) => nextObservable.subscribe(listener));
158+
nextObservable.subscribe(observer);
78159
})
79160
.catch((error) => {
80161
this.debug('Error obtaining auth headers in SlicknodeLink');
81162
observer.error(error);
82163
});
83-
84-
// return forward(operation);
85-
86164
});
87165
}
88166

@@ -203,7 +281,7 @@ export default class SlicknodeLink extends ApolloLink {
203281
/**
204282
* Clears all tokens in the storage
205283
*/
206-
public async logout(forward: NextLink): Promise<void> {
284+
public async logout(): Promise<void> {
207285
this.storage.removeItem(this.namespace + REFRESH_TOKEN_KEY);
208286
this.storage.removeItem(this.namespace + REFRESH_TOKEN_EXPIRES_KEY);
209287
this.storage.removeItem(this.namespace + ACCESS_TOKEN_KEY);

yarn.lock

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,12 @@ apollo-utilities@^1.0.0:
156156
fast-json-stable-stringify "^2.0.0"
157157
fclone "^1.0.11"
158158

159+
apollo-utilities@^1.0.26:
160+
version "1.0.26"
161+
resolved "https://registry.yarnpkg.com/apollo-utilities/-/apollo-utilities-1.0.26.tgz#589c66bf4d16223531351cf667a230c787def1da"
162+
dependencies:
163+
fast-json-stable-stringify "^2.0.0"
164+
159165
append-transform@^1.0.0:
160166
version "1.0.0"
161167
resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-1.0.0.tgz#046a52ae582a228bd72f58acfbe2967c678759ab"

0 commit comments

Comments
 (0)