• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

hicommonwealth / commonwealth / 12913607989

22 Jan 2025 05:21PM UTC coverage: 47.273% (-0.4%) from 47.698%
12913607989

Pull #10529

github

web-flow
Merge 3e0cc764b into 2dceaabc4
Pull Request #10529: Add contest bot webhook

1359 of 3231 branches covered (42.06%)

Branch coverage included in aggregate %.

7 of 61 new or added lines in 6 files covered. (11.48%)

39 existing lines in 10 files now uncovered.

2697 of 5349 relevant lines covered (50.42%)

36.14 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.51
/libs/model/src/user/SignIn.command.ts
1
import { type Command } from '@hicommonwealth/core';
2
import * as schemas from '@hicommonwealth/schemas';
3
import { deserializeCanvas } from '@hicommonwealth/shared';
4
import crypto from 'crypto';
5
import { Op } from 'sequelize';
6
import { config } from '../config';
7
import { models } from '../database';
8
import { mustExist } from '../middleware/guards';
9
import {
10
  transferOwnership,
11
  verifyAddress,
12
  verifySessionSignature,
13
  type VerifiedAddress,
14
} from '../services/session';
15
import { emitEvent } from '../utils/utils';
16

17
/**
18
 * SignIn command for signing in to a community
19
 *
20
 * Before executing the body, it validates that the address is
21
 * compatible with the community and has a valid signature.
22
 *
23
 * - When address-community link found in database:
24
 *   - Verifies existing address
25
 *     - same wallet?
26
 *     - session signature
27
 *   - Transfers ownership of unverified address links to user
28
 *
29
 *  - When address-community link not found in database:
30
 *   - Creates a new link to the community with session/address verification (JoinCommunity is a secured route)
31
 *   - Verifies session signature (proof of address)
32
 *     - Creates a new user if none exists for this address
33
 */
34
export function SignIn(): Command<typeof schemas.SignIn> {
35
  return {
15✔
36
    ...schemas.SignIn,
37
    secure: true,
38
    auth: [],
39
    authStrategy: {
40
      type: 'custom',
41
      name: 'SignIn',
42
      userResolver: async (payload, signedInUser) => {
43
        const { community_id, address } = payload;
×
44
        // TODO: SECURITY TEAM: we should stop many attacks here!
45
        const auth = await verifyAddress(community_id, address.trim());
×
46
        // return the signed in user or a dummy placeholder
47
        return {
×
48
          id: signedInUser?.id ?? -1,
×
49
          email: signedInUser?.email ?? '',
×
50
          auth,
51
        };
52
      },
53
    },
54
    body: async ({ actor, payload }) => {
55
      if (!actor.user.id || !actor.user.auth) throw Error('Invalid address');
15!
56

57
      const { community_id, wallet_id, referrer_address, session, block_info } =
58
        payload;
15✔
59
      const { base, encodedAddress, ss58Prefix, hex, existingHexUserId } = actor
15✔
60
        .user.auth as VerifiedAddress;
61

62
      const was_signed_in = actor.user.id > 0;
15✔
63
      let user_id = was_signed_in ? actor.user.id : (existingHexUserId ?? null);
15✔
64

65
      await verifySessionSignature(
15✔
66
        deserializeCanvas(session),
67
        encodedAddress,
68
        ss58Prefix,
69
      );
70

71
      const verification_token = crypto.randomBytes(18).toString('hex');
13✔
72
      const verification_token_expires = new Date(
13✔
73
        +new Date() + config.AUTH.ADDRESS_TOKEN_EXPIRES_IN * 60 * 1000,
74
      );
75

76
      // upsert address, passing user_id if signed in
77
      const { user_created, address_created, first_community } =
78
        await models.sequelize.transaction(async (transaction) => {
13✔
79
          // is same address found in a different community?
80
          const is_first_community = !(
13✔
81
            user_id ||
18✔
82
            (await models.Address.findOne({
83
              where: {
84
                community_id: { [Op.ne]: community_id },
85
                address: encodedAddress,
86
              },
87
              attributes: ['community_id'],
88
              transaction,
89
            }))
90
          );
91

92
          /* If address doesn't have an associated user, create one!
93
          - NOTE: magic strategy is the other place (when using email)
94
          */
95
          let new_user = false;
13✔
96
          if (!user_id) {
13✔
97
            const existing = await models.Address.findOne({
5✔
98
              where: { address: encodedAddress, user_id: { [Op.ne]: null } },
99
            });
100
            if (!existing) {
5!
101
              const user = await models.User.create(
5✔
102
                {
103
                  email: null,
104
                  profile: {},
105
                  referred_by_address: referrer_address,
106
                },
107
                { transaction },
108
              );
109
              if (!user) throw new Error('Failed to create user');
5!
110
              user_id = user.id!;
5✔
111
              new_user = true;
5✔
UNCOV
112
            } else user_id = existing.user_id!;
×
113
          }
114

115
          const [addr, new_address] = await models.Address.findOrCreate({
13✔
116
            where: { community_id, address: encodedAddress },
117
            defaults: {
118
              community_id,
119
              address: encodedAddress,
120
              user_id,
121
              hex,
122
              wallet_id,
123
              verification_token,
124
              verification_token_expires,
125
              block_info,
126
              last_active: new Date(),
127
              verified: new Date(),
128
              role: 'member',
129
              is_user_default: false,
130
              ghost_address: false,
131
              is_banned: false,
132
            },
133
            transaction,
134
          });
135
          if (!new_address) {
13✔
136
            addr.user_id = user_id;
8✔
137
            addr.wallet_id = wallet_id;
8✔
138
            addr.verification_token = verification_token;
8✔
139
            addr.verification_token_expires = verification_token_expires;
8✔
140
            addr.block_info = block_info;
8✔
141
            addr.last_active = new Date();
8✔
142
            addr.verified = new Date();
8✔
143
            await addr.save({ transaction });
8✔
144
          }
145

146
          const transferredUser = await transferOwnership(addr, transaction);
13✔
147

148
          const events: schemas.EventPairs[] = [];
13✔
149
          new_address &&
13✔
150
            events.push({
151
              event_name: schemas.EventNames.CommunityJoined,
152
              event_payload: {
153
                community_id,
154
                user_id: addr.user_id!,
155
                created_at: addr.created_at!,
156
              },
157
            });
158
          new_user &&
13✔
159
            events.push({
160
              event_name: schemas.EventNames.UserCreated,
161
              event_payload: {
162
                community_id,
163
                address: addr.address,
164
                user_id,
165
                created_at: addr.created_at!,
166
                referrer_address,
167
              },
168
            });
169
          transferredUser &&
13✔
170
            events.push({
171
              event_name: schemas.EventNames.AddressOwnershipTransferred,
172
              event_payload: {
173
                community_id,
174
                address: addr.address,
175
                user_id: addr.user_id!,
176
                old_user_id: transferredUser.id!,
177
                old_user_email: transferredUser.email,
178
                created_at: new Date(),
179
              },
180
            });
181
          await emitEvent(models.Outbox, events, transaction);
13✔
182

183
          return {
13✔
184
            user_created: new_user,
185
            address_created: !!new_address,
186
            first_community: is_first_community,
187
          };
188
        });
189

190
      const addr = await models.Address.scope('withPrivateData').findOne({
13✔
191
        where: { community_id, address: encodedAddress, user_id },
192
        include: [
193
          {
194
            model: models.User,
195
            required: true,
196
            attributes: ['id', 'email', 'profile'],
197
          },
198
        ],
199
      });
200
      mustExist('Address', addr);
13✔
201
      return {
13✔
202
        ...addr.toJSON(),
203
        community_base: base,
204
        community_ss58_prefix: ss58Prefix,
205
        was_signed_in,
206
        user_created,
207
        address_created,
208
        first_community,
209
      };
210
    },
211
  };
212
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc