📦 EqualifyEverything / equalify

📄 reviewAccessRequest.ts · 102 lines
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102import { db, event, isStaging, sendEmail } from "#src/utils";

//
// Admin-only: approve or deny an access request. Approving creates an invite,
// which is what authorizes the email's SSO sign-in (see ensureSsoUser).
//

export const reviewAccessRequest = async () => {
    const { id, action } = event.body;
    if (!['approve', 'deny'].includes(action)) {
        return { status: 'error', message: 'Invalid action.' };
    }

    await db.connect();
    const requesterType = (await db.query({
        text: `SELECT type FROM users WHERE id=$1`,
        values: [event.claims.sub],
    })).rows?.[0]?.type;
    if (requesterType !== 'admin') {
        await db.clean();
        return { status: 'error', message: 'Admin access required.' };
    }

    const request = (await db.query({
        text: `SELECT "id", "name", "email", "status" FROM "access_requests" WHERE "id"=$1`,
        values: [id],
    })).rows?.[0];
    if (!request) {
        await db.clean();
        return { status: 'error', message: 'Access request not found.' };
    }
    if (request.status !== 'pending') {
        await db.clean();
        return { status: 'error', message: 'This request has already been reviewed.' };
    }

    if (action === 'deny') {
        await db.query({
            text: `UPDATE "access_requests" SET "status"='denied', "reviewed_by"=$2, "reviewed_at"=now(), "updated_at"=now() WHERE "id"=$1`,
            values: [id, event.claims.sub],
        });
        await db.clean();
        return { status: 'success', message: 'Request denied.' };
    }

    // approve: create the invite unless one already exists for this email
    const inviteExists = (await db.query({
        text: `SELECT id FROM invites WHERE lower(email)=lower($1)`,
        values: [request.email],
    })).rows?.[0]?.id;
    if (!inviteExists) {
        await db.query({
            text: `INSERT INTO "invites" ("user_id", "email", "name") VALUES ($1, $2, $3)`,
            values: [event.claims.sub, request.email, request.name],
        });
    }
    await db.query({
        text: `UPDATE "access_requests" SET "status"='approved', "reviewed_by"=$2, "reviewed_at"=now(), "updated_at"=now() WHERE "id"=$1`,
        values: [id, event.claims.sub],
    });
    await db.clean();

    // notify the requester; the approval already succeeded, so an email failure
    // shouldn't surface as an error to the reviewing admin
    try {
        await sendEmail({
        to: request.email,
        subject: `Your Equalify access request was approved`,
        body: `<tr>
            <td style="padding:24px 24px 8px 24px; font-size:16px; line-height:1.5; color:#334155;">
              Hello,
            </td>
          </tr>
          <tr>
            <td style="padding:0 24px 24px 24px; font-size:16px; line-height:1.5; color:#334155;">
              Your request to access Equalify has been approved. Sign in with your SSO account below to get started:
            </td>
          </tr>

          <!-- Button -->
          <tr>
            <td align="left" style="padding:0 24px 24px 24px;">
              <table role="presentation" cellpadding="0" cellspacing="0" border="0">
                <tr>
                  <td align="center" bgcolor="#186121" style="border-radius:6px;">
                    <a href="${process.env.APP_URL ?? `https://app${isStaging ? '-staging' : ''}.equalify.uic.edu`}/login"
                       style="display:inline-block; padding:12px 24px; font-size:16px; font-weight:600; color:#ffffff; text-decoration:none; border-radius:6px; background-color:#186121;">
                      Sign In
                    </a>
                  </td>
                </tr>
              </table>
            </td>
          </tr>`
        });
    } catch (emailError) {
        console.error('Approval email failed to send:', request.email, emailError);
    }

    return { status: 'success', message: 'Request approved — invite created.' };
};