Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/controllers.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ Controllers.editStrategy = async (req, res) => {

payload.enabled = !!req.body.enabled;

const checkboxes = ['forceUsernameViaEmail', 'usernameViaEmail', 'trustEmailVerified', 'syncFullname', 'syncPicture'];
const checkboxes = ['forceUsernameViaEmail', 'usernameViaEmail', 'trustEmailVerified', 'syncFullname', 'syncPicture', 'fetchPictureWithToken'];
checkboxes.forEach((prop) => {
payload[prop] = payload.hasOwnProperty(prop) && payload[prop] === 'on' ? 1 : 0;
});
Expand Down
44 changes: 44 additions & 0 deletions library.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ OAuth.getUserProfile = function (name, userRoute, accessToken, done) {
const json = JSON.parse(body);
const profile = await OAuth.parseUserReturn(name, json);
profile.provider = name;
profile.accessToken = accessToken;
done(null, profile);
} catch (e) {
done(e);
Expand Down Expand Up @@ -277,11 +278,54 @@ OAuth.assignGroups = async ({ user, profile }) => {
winston.verbose(`[plugins/sso-auth0] uid ${uid} now a part of ${toJoin.length} these user groups: ${toJoin.join(', ')}`);
};

OAuth.syncPictureViaToken = async (uid, profile, strategy) => {
const enabled = parseInt(strategy.syncPicture, 10) && parseInt(strategy.fetchPictureWithToken, 10);
if (!enabled || !profile.picture || !profile.accessToken) {
return false;
}

const { uploadedpicture } = await user.getUserFields(uid, ['uploadedpicture']);
if (uploadedpicture) {
return true;
}

try {
const res = await fetch(profile.picture, {
headers: { Authorization: `Bearer ${profile.accessToken}` },
});
if (!res.ok) {
winston.verbose(`[plugin/sso-oauth2-multiple] Picture for uid ${uid} unavailable (${res.status})`);
return false;
}

const buffer = Buffer.from(await res.arrayBuffer());
if (!buffer.length) {
return false;
}

const type = (res.headers.get('content-type') || 'image/jpeg').split(';')[0];
await user.uploadCroppedPicture({
callerUid: uid,
uid,
imageData: `data:${type};base64,${buffer.toString('base64')}`,
});

return true;
} catch (err) {
winston.warn(`[plugin/sso-oauth2-multiple] Unable to sync picture for uid ${uid}: ${err.message}`);
return false;
}
};

OAuth.updateProfile = async (uid, profile) => {
const fields = ['fullname', 'picture'];
const strategy = await OAuth.getStrategy(profile.provider);
const allowList = [];

if (await OAuth.syncPictureViaToken(uid, profile, strategy)) {
fields.splice(fields.indexOf('picture'), 1);
}

const payload = fields.reduce((memo, field) => {
const setting = `sync${field[0].toUpperCase()}${field.slice(1)}`;
if (strategy[setting] && parseInt(strategy[setting], 10)) {
Expand Down
12 changes: 12 additions & 0 deletions static/templates/partials/edit-oauth2-strategy.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,18 @@
<input type="checkbox" class="form-check-input" id="syncPicture" name="syncPicture" {{{ if (./syncPicture == "1") }}}checked{{{ end }}}>
<label for="syncPicture" class="form-check-label">Picture</label>
</div>

<div class="form-check form-switch mb-3">
<input type="checkbox" class="form-check-input" id="fetchPictureWithToken" name="fetchPictureWithToken" {{{ if (./fetchPictureWithToken == "1") }}}checked{{{ end }}}>
<label for="fetchPictureWithToken" class="form-check-label">
Download the picture using the access token instead of storing its URL
<p class="form-text">
Required when the picture URL is not publicly reachable, e.g. Microsoft Graph's
<code>/me/photo/$value</code>. The image is uploaded to this forum once, and is not
overwritten if the user later sets their own avatar.
</p>
</label>
</div>
</div>
</div>
</details>
Expand Down