aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/client/apis/GoogleAuthenticationManager.scss20
-rw-r--r--src/client/apis/GoogleAuthenticationManager.tsx29
-rw-r--r--src/server/apis/google/GoogleApiServerUtils.ts51
-rw-r--r--src/server/database.ts5
-rw-r--r--src/server/index.ts3
5 files changed, 91 insertions, 17 deletions
diff --git a/src/client/apis/GoogleAuthenticationManager.scss b/src/client/apis/GoogleAuthenticationManager.scss
index 5efb3ab3b..13bde822d 100644
--- a/src/client/apis/GoogleAuthenticationManager.scss
+++ b/src/client/apis/GoogleAuthenticationManager.scss
@@ -1,3 +1,19 @@
-.paste-target {
- padding: 5px;
+.authorize-container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+
+ .paste-target {
+ padding: 5px;
+ width: 100%;
+ }
+
+ .avatar {
+ border-radius: 50%;
+ }
+
+ .welcome {
+ font-style: italic;
+ margin-top: 15px;
+ }
} \ No newline at end of file
diff --git a/src/client/apis/GoogleAuthenticationManager.tsx b/src/client/apis/GoogleAuthenticationManager.tsx
index d143d8273..01dac3996 100644
--- a/src/client/apis/GoogleAuthenticationManager.tsx
+++ b/src/client/apis/GoogleAuthenticationManager.tsx
@@ -19,6 +19,8 @@ export default class GoogleAuthenticationManager extends React.Component<{}> {
@observable private clickedState = false;
@observable private success: Opt<boolean> = undefined;
@observable private displayLauncher = true;
+ @observable private avatar: Opt<string> = undefined;
+ @observable private username: Opt<string> = undefined;
private set isOpen(value: boolean) {
runInAction(() => this.openState = value);
@@ -40,10 +42,14 @@ export default class GoogleAuthenticationManager extends React.Component<{}> {
authenticationCode => {
if (authenticationCode) {
Identified.PostToServer(RouteStore.writeGoogleAccessToken, { authenticationCode }).then(
- token => {
+ ({ access_token, avatar, name }) => {
+ runInAction(() => {
+ this.avatar = avatar;
+ this.username = name;
+ });
this.beginFadeout();
disposer();
- resolve(token);
+ resolve(access_token);
},
action(() => {
this.hasBeenClicked = false;
@@ -61,15 +67,18 @@ export default class GoogleAuthenticationManager extends React.Component<{}> {
beginFadeout = action(() => {
this.success = true;
+ this.authenticationCode = undefined;
+ this.displayLauncher = false;
+ this.hasBeenClicked = false;
setTimeout(action(() => {
this.isOpen = false;
- this.displayLauncher = false;
setTimeout(action(() => {
this.success = undefined;
this.displayLauncher = true;
- this.hasBeenClicked = false;
+ this.avatar = undefined;
+ this.username = undefined;
}), 500);
- }), 2000);
+ }), 3000);
});
constructor(props: {}) {
@@ -88,7 +97,7 @@ export default class GoogleAuthenticationManager extends React.Component<{}> {
private get renderPrompt() {
return (
- <div style={{ display: "flex", flexDirection: "column" }}>
+ <div className={'authorize-container'}>
{this.displayLauncher ? <button
className={"dispatch"}
onClick={this.handleClick}
@@ -99,6 +108,14 @@ export default class GoogleAuthenticationManager extends React.Component<{}> {
onChange={this.handlePaste}
placeholder={prompt}
/> : (null)}
+ {this.avatar ? <img
+ className={'avatar'}
+ src={this.avatar}
+ /> : (null)}
+ {this.username ? <span
+ className={'welcome'}
+ >Welcome to Dash, {this.username}
+ </span> : (null)}
</div>
);
}
diff --git a/src/server/apis/google/GoogleApiServerUtils.ts b/src/server/apis/google/GoogleApiServerUtils.ts
index 963c7736a..5714c9928 100644
--- a/src/server/apis/google/GoogleApiServerUtils.ts
+++ b/src/server/apis/google/GoogleApiServerUtils.ts
@@ -25,7 +25,8 @@ export namespace GoogleApiServerUtils {
'drive.file',
'photoslibrary',
'photoslibrary.appendonly',
- 'photoslibrary.sharing'
+ 'photoslibrary.sharing',
+ 'userinfo.profile'
];
export const parseBuffer = (data: Buffer) => JSON.parse(data.toString());
@@ -96,21 +97,61 @@ export namespace GoogleApiServerUtils {
});
};
- export const ProcessClientSideCode = async (information: CredentialInformation, authenticationCode: string): Promise<TokenResult> => {
+ export interface GoogleAuthenticationResult {
+ access_token: string;
+ avatar: string;
+ name: string;
+ }
+ export const ProcessClientSideCode = async (information: CredentialInformation, authenticationCode: string): Promise<GoogleAuthenticationResult> => {
const oAuth2Client = await RetrieveOAuthClient(information);
- return new Promise<TokenResult>((resolve, reject) => {
+ return new Promise<GoogleAuthenticationResult>((resolve, reject) => {
oAuth2Client.getToken(authenticationCode, async (err, token) => {
if (err || !token) {
reject(err);
return console.error('Error retrieving access token', err);
}
oAuth2Client.setCredentials(token);
- await Database.Auxiliary.GoogleAuthenticationToken.Write(information.userId, token);
- resolve({ token, client: oAuth2Client });
+ const enriched = injectUserInfo(token);
+ await Database.Auxiliary.GoogleAuthenticationToken.Write(information.userId, enriched);
+ const { given_name, picture } = enriched.userInfo;
+ resolve({
+ access_token: enriched.access_token!,
+ avatar: picture,
+ name: given_name
+ });
});
});
};
+ /**
+ * It's pretty cool: the credentials id_token is split into thirds by periods.
+ * The middle third contains a base64-encoded JSON string with all the
+ * user info contained in the interface below. So, we isolate that middle third,
+ * base64 decode with atob and parse the JSON.
+ * @param credentials the client credentials returned from OAuth after the user
+ * has executed the authentication routine
+ */
+ const injectUserInfo = (credentials: Credentials): EnrichedCredentials => {
+ const userInfo = JSON.parse(atob(credentials.id_token!.split(".")[1]));
+ return { ...credentials, userInfo };
+ };
+
+ export type EnrichedCredentials = Credentials & { userInfo: UserInfo };
+ export interface UserInfo {
+ at_hash: string;
+ aud: string;
+ azp: string;
+ exp: number;
+ family_name: string;
+ given_name: string;
+ iat: number;
+ iss: string;
+ locale: string;
+ name: string;
+ picture: string;
+ sub: string;
+ }
+
export const RetrieveCredentials = (information: CredentialInformation) => {
return new Promise<TokenResult>((resolve, reject) => {
readFile(information.credentialsPath, async (err, credentials) => {
diff --git a/src/server/database.ts b/src/server/database.ts
index 990441d5a..db86b472d 100644
--- a/src/server/database.ts
+++ b/src/server/database.ts
@@ -4,6 +4,7 @@ import { Opt } from '../new_fields/Doc';
import { Utils, emptyFunction } from '../Utils';
import { DashUploadUtils } from './DashUploadUtils';
import { Credentials } from 'google-auth-library';
+import { GoogleApiServerUtils } from './apis/google/GoogleApiServerUtils';
export namespace Database {
@@ -259,8 +260,8 @@ export namespace Database {
return SanitizedSingletonQuery<StoredCredentials>({ userId }, GoogleAuthentication, removeId);
};
- export const Write = async (userId: string, token: any) => {
- return Instance.insert({ userId, canAccess: [], ...token }, GoogleAuthentication);
+ export const Write = async (userId: string, enrichedCredentials: GoogleApiServerUtils.EnrichedCredentials) => {
+ return Instance.insert({ userId, canAccess: [], ...enrichedCredentials }, GoogleAuthentication);
};
export const Update = async (userId: string, access_token: string, expiry_date: number) => {
diff --git a/src/server/index.ts b/src/server/index.ts
index 86c226a21..010a851bc 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -958,8 +958,7 @@ app.get(RouteStore.readGoogleAccessToken, async (req, res) => {
app.post(RouteStore.writeGoogleAccessToken, async (req, res) => {
const userId = req.header("userId")!;
const information = { credentialsPath, userId };
- const { token } = await GoogleApiServerUtils.ProcessClientSideCode(information, req.body.authenticationCode);
- res.send(token.access_token);
+ res.send(await GoogleApiServerUtils.ProcessClientSideCode(information, req.body.authenticationCode));
});
const tokenError = "Unable to successfully upload bytes for all images!";